Анимация
JavaScript
|
Главная Библионтека • в примере 17-3(а) вы не сможете сделать этого без изменения дизайна класса и всего использующего его кода. • В примере 17-3(6) вы просто помещаете необходимую функциональность в функции-члены Fi rst и Second. Такое изменение прозрачно для всех пользователей класса couple. Максимум, что потребуется от них, - перекомпиляция приложения; им не придется вносить никаких изменений в свой код. Оказывается, что пример 17-3(6) имеет и другие практические преимущества. Например, как отмстил Ник Мейн (Nick Mein): "Вы можете поместить точку останова в функцию доступа и выяснить, где и когда происходит изменение значения, что очень помогает в отслеживании ошибок". Такое случается, и довольно часто. Резюме За исключением случая структуры в стиле С (в которой все члены открыты), все члены-данные всегда должны быть закрыты. Поступая иначе, вы нарушаете все принципы инкапсуляции, о которых шла речь в начале этой задачи, и создаете зависимости от имен членов, которые впоследствии затруднят выполнение корректной инкапсуляции. Не существует никаких причин для использования открытых и защищенных членов-данных; они всегда могут быть тривиально обернуты в (изначально) встраиваемые функции доступа, которые не приводят ни к каким дополнительным расходам, так что лучше всегда делать все правильно с самого начала. (Правда, имеются примеры защищенных членов-данных в стандартной библиотеке. Эти примеры не могут служить образцом.) Начинайте с правильного проектирования интерфейса. Внутренние детали легко можно исправить и позже, но возможности исправить интерфейс у вас может больше не оказаться. Задача 18. Виртуальность Сложность: 7 в этой задаче мы возвращаемся к старым вопросам, чтобы дать на них новые и/или улучшенные ответы. В этой мы сформулируем четыре рекомендации по проектированию классов, которые отвечают на ряд вопросов. Почему интерфейсы должны быть невиртуальными? Почему виртуальные функции должны быть закрытыми? Остается ли в силе старый совет по поводу деструкторов? Вопрос для новичка 1. В чем заключается "обычный совет" по поводу деструкторов базовых классов? Вопрос для профессионала 2. Когда виртуальные функции должны быть открытыми, защищенными, закрытыми? Поясните ваш ответ. Решение В этой задаче я хочу представить современный взгляд на два часто повторяющихся вопроса о виртуальных функциях. Отвечая на эти вопросы, мы сформулируем четыре рекомендации по проектированию классов. Не будучи новыми, эти вопросы остаются актуальными, хотя отвечаем мы на них сегодня по-другому, с учетом опыта работы с современным С++. Обычный совет о деструкторах базовых классов 1. В чем заключается "обычный совет" по поводу деструкторов базовых классов? Я знаю, что вы уже знакомы с вопросом: должны ли деструкторы базовых классов быть виртуальными? Это не только часто задаваемый, но и весьма жарко обсуждаемый вопрос. Обыч-ный ответ на него: "Конечно, деструкторы базовых классов должны быть виртуальными!" Это неправильный ответ, и даже стандартная библиотека С++ содержит массу контрпримеров. Тем не менее, деструкторы базовых классов достаточно часто виртуальны, чтобы создать иллюзию правильности процитированного ответа. Мы вскоре вернемся к этому вопросу. Это второй из двух вопросов о доступности виртуальных функций. Начнем с более общего вопроса. Виртуальный вопрос №1: открытость или закрытость? Общий вопрос, который мы должны рассмотреть, формулируется следующим образом. 2. Когда виртуальные функции должны быть открытыми, зашишенными, закрытыми? Поясните ваш ответ. Краткий ответ звучит так: редко (если вообще должны); иногда; по умолчанию, соответственно. Словом, тот же ответ, что и для членов класса других типов. Большинство из нас на собственном горьком опыте научились делать все члены класса закрытыми по умолшнию, кроме тех, которые мы действительно хотим предоставить для всеобщего пользования. Это стиль хорошей инкапсуляции. Конечно, мы давно знаем, что члены-данные должны всегда быть закрытыми (кроме случая структур в стиле С, которые представляют собой просто удобный способ фуппирования данных; см. задачу 17). То же самое правило применимо и для функций-членов, так что я предлагаю следующие рекомендации, выражающие преимущества "приватизации" кода. > Рекомендация Предпочтительно делать интерфейс невиртуальным. Обратите внимание - я сказал "предпочтительно", а не "всегда". Интересно, что стандартная библиотека С++- следует этой рекомендации. Не считая деструкторов (которые рассматриваются отдельно в рекомендации №4) и не учитывая дублирования виртуальных функций, которые участвуют в специализациях шаблонов классов, в стандартной библиотеке есть: • 6 открытых виртуальных функций; все они представляют собой std::exception::what и с с перекрытия; и • 142 неоткрытых виртуальных функций. Недавно мне встретился еще один пример. Когда я писал эту книгу, я работал на Microsoft над С++-аспектами платформы .NET и .NET Frameworks (FX), которые выросли в Win FX, который является объектно-ориентированным наследником Win32 API и программной моделью Longhorn, следующего поколения операционной системы Windows. Win FX даже в текущем состоянии разработки представляет собой огромный API - уже сейчас в нем более 14000 классов и около 100000 функций-членов (включающих в себя текущее сосгояние .NET Frameworks и многое другое). Это действительно много. Вы не ошибетесь, если спросите - не слишком ли это монстрооб-разно, но сейчас дело не в этом. Вот почему я упомянул . N ЕТ Frameworks и его эволюцию в Win FX. Для такого монстра, как эта библиотека классов, единственная надежда на работоспособность заключается в повсеместном строго соблюдающемся хорошем дизайне классов. Я счастлив сообщить, что так оно и есть. Вот одна из рекомендаций проектирования Win FX, которая кажется удивительно знакомой, и хотя я согласен с ней, я не имею никакого отношения к ее принятию - она принята по не зависящим от меня обстоятельствам. Рекомендуется обеспечивать настройку посредством защищенных методов. Открытый интерфейс базового класса должен обеспечивать богатый набор функциональных возможностей для потребителя этого класса. Однако наст/юйка класса для предоставления богатых функциональных возможностей потребителю должна обеспечиваться реализацией минимально возможного количества методов. Для достижения этой цели следует обеспечить набор невиртуальных или финальных открытых методов, каждый из которых вызывает единственный защищенный метод (член семейства методов) с суффиксом core , который и реализует данный метод. Эта технология известна как метод шаблона (.NET Framework (WinFX) Design Guidelines, январь 2004). Давайте рассмотрим эту практическую рекомендацию подробнее. Традиционно многие программисты используют базовые классы с открытыми виртуальными функциями. Например, мы можем написать следующий код. пример 18-1: традиционный базовый класс class widget { public: каждая из этих функций может (не обязательно) быть чисто виртуальной, и в этом случае она может быть реализована в widget, а может и не быть - см. [Sutter02]. vi rtual int process( Gadget* ); Имеются в виду функции Java/C#. - Прим. перев. Задача 18. Виртуальность 119 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [ 34 ] 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |