|
Программирование >> Обобщенные обратные вызовы
temp!ate<с1ass T, class U> class Couple { public: Основные члены-данные открыты... т fi rst; и second; ...но есть еще нечто, делающее его более классоподобным , а также закрытая реализация couple О : deleted (false ) { void MarkDeleted() { deleted = true; ] bool isDeleted () { return deleted ; } private: bool deleted.; Должны ли в этом случае остальные члены-данные быть, как показано в коде, открытыми? Почему? Если да, то является ли этот код хорошим примером того, почему иногда одновременное наличие открытых и закрытых данных в одном классе может быть удачным решением? Рассматриваемый класс Couple предложен в качестве контрпримера к обычной рекомендации по кодированию. Здесь приведен класс, который является почти структурой , но имеет некоторые закрытые служебные данные. Эти данные (в приведенном примере -- простой атрибут) имеют инвариант. Утверждается, что обновление атрибута происходит абсолютно независимо от обновления открытых членов класса coup! е. Начнем с утверждения об абсолютной независимости. Я не согласен! Обновление может быть независимым, но понятно, что сам атрибут не является независимым от этих значений; иначе он бы не был сгруппирован с ними в один объект. Атрибут de-leted - приложение к сопутствующим объектам. Вот как мы можем решить поставленную задачу с использованием функций доступа. пример 17-3(6): корректная инкапсуляция, изначально использующая встраиваемые функции доступа, позже при необходимости эти функции могут превратиться в нетривиальный код (или остаться такими, как есть). tempiate<class т, class u> class couple { coupleO : deleted (false) { } return first ; } return second ; } deleted. = true; } return deleted.; } T& Fi rst() U& SecondO void MarkDeleted() bool IsDeletedO private: T first.; и second.; bool deleted.; Ну и зачем беспокоиться о ничего не делающих функциях доступа? - мог бы спросить, не подумав, кто-то из читателей. Отвечаю. Как уже упоминалось в обсуждении примера 17-2(6), если сегодня вызывающему коду потребовалось изменение некоторого аспекта данного объекта (в данном примере атрибут deleted.), то завтра может потребоваться добавление в него новых возможностей - даже если это будут всего лишь средства для отладки. Пример 17-3(6) обеспечивает вас необходимой гибкостью, которая не вызывает лишних затрат из-за использования встраиваемых функций. Пусть, например, месяц спустя вам потребуется фиксировать все обращения к объектам, помеченным как удаленные. в примере 17-3(а) вы не сможете сделать этого без изменения дизайна класса и всего использующего его кода. В примере 17-3(6) вы просто помещаете необходимую функциональность в функции-члены Fi rst и Second. Такое изменение прозрачно для всех пользователей класса couple. Максимум, что потребуется от них, - перекомпиляция приложения; им не придется вносить никаких изменений в свой код. Оказывается, что пример 17-3(6) имеет и другие практические преимущества. Например, как отмстил Ник Мейн (Nick Mein): Вы можете поместить точку останова в функцию доступа и выяснить, где и когда происходит изменение значения, что очень помогает в отслеживании ошибок . Такое случается, и довольно часто. Резюме За исключением случая структуры в стиле С (в которой все члены открыты), все члены-данные всегда должны быть закрыты. Поступая иначе, вы нарушаете все принципы инкапсуляции, о которых шла речь в начале этой задачи, и создаете зависимости от имен членов, которые впоследствии затруднят выполнение корректной инкапсуляции. Не существует никаких причин для использования открытых и защищенных членов-данных; они всегда могут быть тривиально обернуты в (изначально) встраиваемые функции доступа, которые не приводят ни к каким доп ол н ител ьн ы .м расходам, так что лучше всегда делать все правильно с самого начала. (Правда, имеются примеры защищенных членов-данных в стандартной библиотеке. Эти примеры не могут служить образцом.) Начинайте с правильного проектирования интерфейса. Внутренние детали легко можно исправить и позже, но возможности исправить интерфейс у вас может больше не оказаться. Задача 18. Виртуальность Сложность: 7 в этой задаче мы возвращаемся к старым вопросам, чтобы дать на них новые и/или улучшенные ответы. В этой мы сформулируем четыре рекомендации по проектированию классов, которые отвечают на ряд вопросов. Почему интерфейсы должны быть невиртуальными? Почему виртуальные функции должны быть закрытыми? Остается ли в силе старый совет по поводу деструкторов? Вопрос для новичка 1. В чем заключается обычный совет по поводу деструкторов базовых классов? Вопрос для профессионала 2. Когда виртуальные функции должны быть открытыми, защищенными, закрытыми? Поясните ваш ответ. Решение В этой задаче я хочу представить современный взгляд на два часто повторяющихся вопроса о виртуальных функциях. Отвечая на эти вопросы, мы сформулируем четыре рекомендации по проектированию классов. Не будучи новыми, эти вопросы остаются актуальными, хотя отвечаем мы на них сегодня по-другому, с учетом опыта работы с современным С++. Обычный совет о деструкторах базовых классов 1. В чем заключается обычный совет по поводу деструкторов базовых классов? Я знаю, что вы уже знакомы с вопросом: должны ли деструкторы базовых классов быть виртуальными? Это не только часто задаваемый, но и весьма жарко обсуждаемый вопрос. Обыч-ный ответ на него: Конечно, деструкторы базовых классов должны быть виртуальными! Это неправильный ответ, и даже стандартная библиотека С++ содержит массу контрпримеров. Тем не менее, деструкторы базовых классов достаточно часто виртуальны, чтобы создать иллюзию правильности процитированного ответа. Мы вскоре вернемся к этому вопросу. Это второй из двух вопросов о доступности виртуальных функций. Начнем с более общего вопроса. Виртуальный вопрос №1: открытость или закрытость? Общий вопрос, который мы должны рассмотреть, формулируется следующим образом. 2. Когда виртуальные функции должны быть открытыми, зашишенными, закрытыми? Поясните ваш ответ. Краткий ответ звучит так: редко (если вообще должны); иногда; по умолчанию, соответственно. Словом, тот же ответ, что и для членов класса других типов. Большинство из нас на собственном горьком опыте научились делать все члены класса закрьггыми по умолшнию, кроме тех, которые мы действительно хотим предоставить для всеобщего пользования. Это стиль хорошей инкапсуляции. Конечно, мы давно знаем, что члены-данные должны всегда быть закрытыми (кроме случая структур в стиле С, которые представляют собой просто удобный способ фуппирования данных; см. задачу 17). То же самое правило применимо и для функций-членов, так что я предлагаю следующие рекомендации, выражающие преимущества приватизации кода.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |