Главная страница
Навигация по странице:

  • Делайте все данные закрытыми, а не защищенными

  • Множественное наследование

  • Почему правил наследования так много

  • Методы-члены и данные-члены Ниже я даю несколько советов по эффективной реализации методовчленов и данныхчленов. Включайте в класс как можно меньше методов

  • Блокируйте неявно сгенерированные методы и операторы, которые вам

  • Минимизируйте число разных методов, вызываемых классом

  • Избегайте опосредованных вызовов методов других классов

  • Перекрестная ссылка

  • Вообще минимизируйте сотрудничество класса с дру

  • Инициализируйте по мере возможности все данныечлены во всех кон

  • Создавайте классыодиночки с помощью закрытого

  • Пример создания класса-одиночки с помощью закрытого конструктора (Java)

  • Если сомневаетесь, выполняйте полное копирование, а не ограниченное

  • 6.4. Разумные причины создания классов

  • Моделирование объектов реального мира

  • Моделирование абстрактных объектов

  • Сокрытие деталей реализации

  • Ограничение влияния изменений

  • ЧАСТЬ II

  • Упрощение передачи параметров в методы

  • Создание центральных точек управления

  • Облегчение повторного использования кода

  • Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


    Скачать 5.88 Mb.
    НазваниеРуководство по стилю программирования и конструированию по
    АнкорСовершенный код
    Дата31.03.2023
    Размер5.88 Mb.
    Формат файлаpdf
    Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
    ТипРуководство
    #1028502
    страница21 из 106
    1   ...   17   18   19   20   21   22   23   24   ...   106
    ГЛАВА 6 Классы
    145
    ции это неуместно: имя метода
    DoCommand() было бы настолько туманным, что почти утратило бы всякий смысл, тогда как блоки
    case довольно информативны.
    Делайте все данные закрытыми, а не защищенными Как говорит Джошуа
    Блох, «наследование нарушает инкапсуляцию» (Bloch, 2001). Выполняя наследо#
    вание от класса, вы получаете привилегированный доступ к его защищенным методам и данным. Если производному классу на самом деле нужен доступ к ат#
    рибутам базового класса, включите в базовый класс защищенные методы доступа.
    Множественное наследование
    Наследование — мощный и... довольно опасный инструмент.
    В некотором смысле наследование похоже на цепную пилу:
    при соблюдении мер предосторожности оно может быть невероятно полезным, но при неумелом обращении послед#
    ствия могут оказаться очень и очень серьезными.
    Если наследование — цепная пила, то множественное на#
    следование — это старинная цепная пила с барахлящим мотором, не имеющая предохранителей и не поддержива#
    ющая автоматического отключения. Иногда такой инструмент может пригодить#
    ся, но большую часть времени его лучше хранить в гараже под замком.
    Некоторые эксперты рекомендуют широкое применение множественного насле#
    дования (Meyer, 1997), но по опыту могу сказать, что оно полезно главным обра#
    зом только при создании «миксинов» — простых классов, позволяющих добавить ряд свойств в другой класс. Миксины называются так потому, что они позволяют
    «подмешать (mix in)» свойства в производные классы. Миксинами могут быть классы вроде
    Displayable, Persistent, Serializable или Sortable. Миксины почти всегда явля#
    ются абстрактными и не поддерживают создания экземпляров независимо от других объектов.
    Миксины требуют множественного наследования, но пока все миксины по#насто#
    ящему независимы друг от друга, вы можете не бояться классической проблемы,
    связанной с ромбовидной схемой наследования. Кроме того, «объединяя» атри#
    буты, они делают проект системы понятнее. Программисту легче разобраться с объектом, использующим миксины
    Displayable и Persistent, а не 11 более конкрет#
    ных методов, которые понадобились бы для реализации этих двух свойств в про#
    тивном случае.
    Похоже, разработчики Java и Visual Basic понимали ценность миксинов, разрешив множественное наследование интерфейсов, но только единичное наследование классов. C++ поддерживает множественное наследование и интерфейсов, и реа#
    лизации. Используйте множественное наследование, только тщательно рассмот#
    рев все альтернативные варианты и проанализировав влияние выбранного под#
    хода на сложность и понятность системы.
    Почему правил наследования так много?
    В этом разделе были описаны многие правила избавления от проблем,
    связанных с наследованием. Все эти правила подразумевают, что
    насле%
    дование часто противоречит главному техническому императиву програм%
    С множественным наследовани- ем в C++ связан один неоспори- мый факт: оно открывает ящик
    Пандоры, полный проблем, ко- торые просто невозможны при единичном наследовании.
    Скотт Мейерс
    (Scott Meyers)

    146
    ЧАСТЬ II Высококачественный код
    мирования — управлению сложностью. Ради управления сложностью относитесь к наследованию с подозрением. Вот как использовать наследование и включение:
    
    если несколько классов имеют общие данные, но не фор#
    мы поведения, создайте общий объект, который можно было бы включить во все эти классы;
    
    если несколько классов имеют общие формы поведения,
    но не данные, сделайте эти классы производными от общего базового класса, определяющего общие методы;
    
    если несколько классов имеют общие данные и формы поведения, сделайте эти классы производными от общего базового класса, определяющего общие дан#
    ные и методы;
    
    используйте наследование, если хотите, чтобы интерфейс определялся базо#
    вым классом, и включение, если хотите сами контролировать интерфейс.
    Методы-члены и данные-члены
    Ниже я даю несколько советов по эффективной реализации методов#членов и данных#членов.
    Включайте в класс как можно меньше методов
    В одном исследовании программ на C++ было обнаружено, что большему числу методов в расчете на один класс соответствует большее число изъянов (Basili, Briand,
    and Melo, 1996). Однако важнее оказались другие конкурирующие факторы, в том числе многоуровневые иерархии наследования, большое число методов, вызыва#
    емых из класса, и сильное сопряжение между классами. Разрабатывая класс, стре#
    митесь к оптимальному соответствию между этими факторами и минимальным числом методов.
    Блокируйте неявно сгенерированные методы и операторы, которые вам
    не нужны Иногда некоторые возможности, такие как создание объекта или при#
    сваивание, целесообразно блокировать. Вам может показаться, что сделать это невозможно, потому что компилятор генерирует эти операции автоматически.
    Однако вы можете запретить их использование в клиентском коде, объявив кон#
    структор, оператор присваивания или другой метод или оператор как
    private.
    (Создание закрытого конструктора — стандартный способ определения класса#
    одиночки, о чем см. ниже.)
    Минимизируйте число разных методов, вызываемых классом Одно иссле#
    дование показало, что число дефектов в коде класса статистически коррелирует с общим числом методов, вызываемых классом (Basili, Briand, and Melo, 1996). То же исследование показало, что число дефектов в коде класса повышается и при увеличении числа используемых в нем классов. Эти концепции иногда называют
    «коэффициентом разветвления по выходу (fan out)».
    Избегайте опосредованных вызовов методов других классов Непосред#
    ственные связи довольно опасны. Опосредованные связи, такие как
    account.Con%
    tactPerson().DaytimeContactInfo().PhoneNumber(), опасны еще больше. В связи с этим ученые сформулировали «Правило Деметры (Law of Demeter)» (Lieberherr and
    Holland, 1989), которое гласит, что Объект A может вызывать любые из собствен#
    ных методов. Если он создает Объект B, он может вызывать любые методы Объекта
    Перекрестная ссылка О слож- ности см. подраздел «Главный
    Технический Императив Разра- ботки ПО: управление сложно- стью» раздела 5.2.
    Перекрестная ссылка О методах в общем см. главу 7.

    ГЛАВА 6 Классы
    147
    B, но ему не следует вызывать методы объектов, возвраща#
    емых Объектом B. В нашем случае это означает, что вызов
    account . ContactPerson() приемлем, однако вызова account.%
    ContactPerson() .DaytimeContactInfo() следовало бы избежать.
    Это упрощенное объяснение — подробнее см. в книгах, ука#
    занных в конце главы.
    Вообще минимизируйте сотрудничество класса с дру'
    гими классами Старайтесь свести к минимуму все следу#
    ющие показатели:
    
    число видов создаваемых объектов;
    
    число непосредственно вызываемых методов созданных объектов;
    
    число вызовов методов, принадлежащих объектам, возвращенным другими созданными объектами.
    Конструкторы
    Советы по использованию конструкторов почти не зависят от языка (по крайней мере это касается C++, Java и Visual Basic). С деструкторами связано больше раз#
    личий — см. материалы, указанные в разделе «Дополнительные ресурсы».
    Инициализируйте по мере возможности все данные'члены во всех кон'
    структорах Инициализация всех данных#членов во всех конструкторах — про#
    стой прием защитного программирования.
    Создавайте классы'одиночки с помощью закрытого
    конструктора Если вы хотите определить класс, позво#
    ляющий создать только один объект, скройте все конструк#
    торы класса и создайте статический метод
    GetInstance(), пре#
    доставляющий доступ к единственному экземпляру класса:
    Пример создания класса-одиночки с помощью
    закрытого конструктора (Java)
    public class MaxId {
    // конструкторы и деструкторы
    Закрытый конструктор.
    private MaxId() {
    }
    // открытые методы
    Открытый метод, предоставляющий доступ к единственному экземпляру класса.
    public static MaxId GetInstance() {
    return m_instance;
    }
    // закрытые члены
    Дополнительные сведения Хо- рошее обсуждение «Правила
    Деметры» см. в книгах «Pragma- tic Programmer» (Hunt and Tho- mas, 2000), «Applying UML and
    Patterns» (Larman, 2001) и «Fun- damentals of Object-Oriented De- sign in UML» (Page-Jones, 2000).
    Дополнительные сведения Ана- логичный код, написанный на
    C++, был бы очень похож. Под- робнее см. раздел 26 книги «More
    Effective C++» (Meyers, 1998).
    >
    >

    148
    ЧАСТЬ II Высококачественный код
    Единственный экземпляр класса.
    private static final MaxId m_instance = new MaxId();
    }
    Закрытый конструктор вызывается только при инициализации статического объек#
    та
    m_instance. Для обращения к классу#одиночке MaxId нужно просто вызвать метод
    MaxId. GetInstance().
    Если сомневаетесь, выполняйте полное копирование, а не ограниченное
    Одним из главных аспектов работы со сложными объектами является выбор типа их копирования: полного или ограниченного. Полная копия (deep copy) — это почленная копия данных#членов объекта; ограниченная копия (shallow copy)
    обычно просто указывает или ссылается на исходный объект, хотя конкретные значения «полного» и «ограниченного» копирования могут различаться.
    Мотивом создания ограниченных копий обычно бывает повышение быстродей#
    ствия программы. Однако создание нескольких копий крупных объектов редко приводит к заметному снижению быстродействия, хотя и выглядит эстетически непривлекательно. Полное копирование некоторых объектов действительно мо#
    жет снижать быстродействие, но программисты обычно очень плохо определя#
    ют, какой код вызывает проблемы (см. главу 25). Повышение сложности едва ли можно оправдать сомнительным улучшением быстродействия кода, поэтому, если не доказано обратное, лучше выполнять полное копирование.
    Полные копии легче в реализации и сопровождении, чем ограниченные. При ограниченном копировании нужно написать не только специфический для объекта код, но и код подсчета ссылок, безопасного сравнения объектов, их безопасного уничтожения и т. д. Такой код может быть источником ошибок, поэтому без вес#
    кой причины создавать его не следует.
    Если вы находите, что вам все#таки нужно ограниченное копирование, прекрас#
    ное обсуждение этого подхода в контексте C++ см. в разделе 29 книги Скотта
    Мейерса «More Effective C++» (Meyers, 1996). В книге Мартина Фаулера «Refactoring»
    (Fowler, 1999) описываются специфические действия, нужные для преобразова#
    ния ограниченных копий в полные и наоборот [Фаулер называет их объектами#
    ссылками (reference objects) и объектами#значениями (value objects)].
    6.4. Разумные причины создания классов
    Если вы верите всему, что читаете, у вас могло сложиться впечатление, что единственная причина создания класса —
    моделирование объектов реального мира. На самом деле это весьма далеко от истины. Список разумных причин созда#
    ния класса приведен ниже.
    Моделирование объектов реального мира Пусть моде#
    лирование объектов реального мира — не единственная причина создания класса, но от этого она не становится менее хорошей! Создайте класс для каждого объекта реаль#
    Перекрестная ссылка Причины создания классов и методов во многом перекрываются (см.
    раздел 7.1).
    Перекрестная ссылка Об иден- тификации объектов реального мира см. подраздел «Определи- те объекты реального мира»
    раздела 5.3.
    >

    ГЛАВА 6 Классы
    149
    ного мира, моделируемого вашей программой. Поместите нужные объекту дан#
    ные в класс и создайте сервисные методы, моделирующие поведение объекта.
    Примеры подобного моделирования см. в разделе 6.1.
    Моделирование абстрактных объектов Другой разумной причиной созда#
    ния класса является моделирование
    абстрактного объекта — объекта, который не существует в реальном мире, но является абстракцией других конкретных объек#
    тов. Прекрасный пример — классический объект
    Shape (фигура). Объекты Circle
    (окружность) и
    Square (прямоугольник) существуют на самом деле, тогда как класс
    Shape — это абстракция конкретных фигур.
    В мире программирования редко встречаются готовые абстракции вроде
    Shape, из#
    за чего поиск ясных абстракций усложняется. Процесс извлечения абстракций из разнообразия сущностей реального мира недетерминирован, и формирование абстракций может быть основано на разных принципах. Если бы нам не были из#
    вестны окружности, прямоугольники, треугольники и другие геометрические фи#
    гуры, мы могли бы прийти в итоге к более необычным фигурам, таким как «каба#
    чок», «брюква» и «Понтиак Ацтек». Нахождение адекватных абстрактных объектов
    — одна из главных проблем объектно#ориентированного проектирования.
    Снижение сложности Снижение сложности — самая важная причи#
    на создания класса. Создайте класс для сокрытия информации, чтобы о ней можно было не думать. Конечно, вам придется думать о ней при написании класса, но после этого вы сможете забыть о деталях и использовать класс, не зная о его внутренней работе. Другие причины создания классов —
    минимизация объема кода, облегчение сопровождения программы и снижение числа ошибок — тоже хороши, но без абстрагирующей силы классов сложные программы было бы невозможно охватить умом.
    Изоляция сложности Как бы ни проявлялась сложность — в форме запутан#
    ных алгоритмов, крупных наборов данных, замысловатых протоколов коммуни#
    кации, — она является источником ошибок. При возникновении ошибки ее будет проще найти, если она будет локализована в классе, а не распределена по всему коду. Изменения, обусловленные исправлением ошибки, не повлияют на осталь#
    ной код, потому что вам придется исправить только один класс. Если вы найдете более эффективный, простой или надежный алгоритм, им будет легче заменить старый алгоритм, изолированный в классе. Во время разработки вам будет про#
    ще попробовать несколько вариантов проектирования и выбрать наилучший.
    Сокрытие деталей реализации Еще одна прекрасная причина создания класса
    — сокрытие деталей реализации, и таких сложных, как мудреный способ доступа к БД, и столь банальных, как отдельный элемент данных, хранимый в форме чис#
    ла или строки.
    Ограничение влияния изменений Изолируйте области вероятных изменений,
    чтобы влияние изменений ограничивалось пределами одного или нескольких классов. Проектируйте приложение так, чтобы области вероятных изменений можно было изменить с максимальной легкостью. В число областей вероятных изменений входят зависимости от оборудования, подсистема ввода/вывода, слож#
    ные типы данных и бизнес#правила. Некоторые частые источники изменений

    150
    ЧАСТЬ II Высококачественный код описаны в подразделе «Скрывайте секреты (к вопросу о сокрытии информации)»
    раздела 5.3.
    Сокрытие глобальных данных Используя глобальные данные, вы можете скрыть детали их реализации за интер#
    фейсом класса. Обращение к глобальным данным через ме#
    тоды доступа имеет ряд преимуществ в сравнении с их не#
    посредственным использованием. Вы можете менять структуру данных, не изме#
    няя программу. Вы можете следить за доступом к данным. Кроме того, использо#
    вание методов доступа подталкивает к размышлениям о том, на самом ли деле данные глобальны; часто оказывается, что «глобальные данные» на самом деле являются просто данными какого#то объекта.
    Упрощение передачи параметров в методы Если вы передаете один пара#
    метр в несколько методов, это может указывать на необходимость объединения этих методов в класс, чтобы они могли использовать параметр как данные объекта.
    Упрощение передачи параметров в методы само по себе не цель, но передача крупных объемов данных наводит на мысль, что другая организация классов могла бы быть более эффективной.
    Создание центральных точек управления Управлять каждой задачей в одном месте — разумная идея. Управле#
    ние может принимать разные формы. Знание числа элемен#
    тов таблицы — одна форма. Управление файлами, соедине#
    ниями с БД, принтерами и другими устройствами — другая.
    Использование одного класса для чтения и записи БД явля#
    ется формой централизованного управления. Если БД нужно будет преобразовать в однородный файл или данные «в памяти», изменения придется внести только в один класс.
    Идея централизованного управления похожа на сокрытие информации, но она имеет уникальную эвристическую силу, так что не забудьте добавить ее в свой инструментарий.
    Облегчение повторного использования кода Код, разбитый на грамотно орга#
    низованные классы, легче повторно использовать в других программах, чем тот же код, помещенный в один более крупный класс. Даже если фрагмент вызывает#
    ся только из одного места программы и вполне понятен в составе более крупно#
    го класса, подумайте, может ли он понадобиться в другой программе. Если да, стоит поместить его в отдельный класс.
    В Лаборатории проектирования ПО NASA были изучены десять проек#
    тов, в которых энергично преследовалось повторное использование кода
    (McGarry, Waligora, and McDermott, 1989). И при объектно#, и при функ#
    ционально#ориентированном подходах разработчикам первоначально не удалось достичь этой цели, потому что в предыдущих проектах не была создана достаточная база кода. Впоследствии при работе над функциональными проектами около 35%
    кода удалось взять из предыдущих проектов. В проектах, основанных на объект#
    но#ориентированном подходе, этот показатель составил 70%. Если благодаря заб#
    лаговременному планированию можно избежать написания 70% кода, грех этим не воспользоваться!
    Перекрестная ссылка О сокры- тии информации см. подраздел
    «Скрывайте секреты (к вопро- су о сокрытии информации)»
    раздела 5.3.
    Перекрестная ссылка О пробле- мах, связанных с глобальными данными, см. раздел 13.3.

    1   ...   17   18   19   20   21   22   23   24   ...   106


    написать администратору сайта