Уточните определение классов, указав их зависимости от других классов.
Различные виды зависимостей обсуждаются в $$12.2. Основными по
отношению к проектированию следует считать отношения наследования
и использования. Оба предполагают понимание того, что значит для
класса отвечать за определенное свойство системы. Отвечать за что-либо
не означает, что класс должен содержать в себе всю информацию, или,
что его функции-члены должны сами проводить все необходимые операции.
Как раз наоборот, каждый класс, имеющий определенный уровень
ответственности, организует работу, перепоручая ее в виде
подзадач другим классам, которые имеют меньший уровень ответственности.
Но надо предостеречь, что злоупотребление этим приемом приводит
к неэффективным и плохо понимаемым проектам, поскольку
происходит размножение классов и объектов до такой степени, что
вместо реальной работы производится только серия запросов на
ее выполнение. То, что можно сделать в данном месте, следует
сделать.
Необходимость учесть отношения наследования и использования
на этапе проектирования (а не только в процессе реализации) прямо
вытекает из того, что классы представляют определенные понятия.
Отсюда также следует, что именно компонент (т.е. множество
связанных классов), а не отдельный класс, являются единицей
проектирования.
11.3.3.4 Шаг 4: определение интерфейсов
Определите интерфейсы классов. На этой стадии проектирования не нужно
рассматривать приватные функции. Вопросы реализации, возникающие на
стадии проектирования, лучше всего обсуждать на шаге 3 при
рассмотрении различных зависимостей. Более того, существует
золотое правило: если класс не допускает по крайней мере двух
существенно отличающихся реализаций, то что-то явно не в порядке с этим
классом, это просто замаскированная реализация, а не представление
абстрактного понятия. Во многих случаях для ответа на вопрос:
"Достаточно ли интерфейс класса независим от реализации?"- надо
указать, возможна ли для класса схема ленивых вычислений.
Отметим, что общие базовые классы и друзья (friend) являются
частью общего интерфейса класса (см. $$5.4.1 и $$12.4). Полезным
упражнением может быть определение раздельного интерфейса для
классов-наследников и всех остальных классов с помощью разбиения
интерфейса на общую и закрытые части.
Именно на этом шаге следует продумать и описать точные определения
типов аргументов. В идеале желательно иметь максимальное число
интерфейсов со статическими типами, относящимися к области приложения
(см. $$12.1.3 и $$12.4).
При определении интерфейсов следует обратить внимание на те
классы, где набор операций представлен более, чем на одном уровне
абстракции. Например, в классе file у некоторых функций-членов
аргументы имеют тип file_descriptor (дескриптор_файла), а у других
аргументы - строка символов, которая обозначает имя файла.
Операции с file_descriptor работают на другом уровне (меньшем)
абстракции, чем операции с именем файла, так что даже странно,
что они относятся к одному классу. Возможно, было бы лучше иметь
два класса: один представляет понятие дескриптора файла, а
другой - понятие имени файла. Обычно все операции класса должны
представлять понятия одного уровня абстракции. Если это не так,
то стоит подумать о реорганизации и его, и связанных с ним классов.
11.3.3.5 Перестройка иерархии классов
Шаги 1 и 3 требуют исследования классов и их иерархии, чтобы
убедиться, что они адекватно отвечают нашим требованиям. Обычно
это не так, и приходится проводить перестройку для улучшения
структуры, проекта или реализации.
Самая типичная перестройка иерархии классов состоит в выделении
общей части двух классов в новый класс или в разбиении класса на два
новых. В обоих случаях в результате получится три класса:
базовый класс и два
производных. Когда следует проводить такую перестройку? Каковы
общие показания, что такая перестройка будет полезной?
К сожалению нет простого и универсального ответа на эти
вопросы. Это и не удивительно, поскольку то, что предлагается,
не является мелочью при реализации, а изменяет основные
понятия системы. Важной и нетривиальной задачей является поиск
общности среди классов и выделение общей части. Нет точного
определения общности, но следует обращать внимание на общность
для понятий системы, а не просто для удобства реализации. Указаниями,
что два класса имеют нечто общее, что возможно выделить в общий базовый
класс, служат схожие способы использования, сходство наборов операций,
сходство реализаций и просто тот факт, что часто в процессе обсуждения
проекта оба класса появляются одновременно. С другой
стороны, если есть
несколько наборов операций класса с различными способами использования,
если эти наборы обеспечивают доступ к раздельным подмножествам объектов
реализации, и, если класс возникает в процессе обсуждения несвязанных
тем, то этот класс является явным кандидатом для разбиения на части.
В силу тесной связи между понятиями и классами проблемы
перестройки иерархии классов высвечиваются на поверхности проблем
именования классов и использования имен классов в процессе обсуждения
проекта. Если имена классов и их упорядоченность, задаваемая иерархией
классов, кажутся неудобными при обсуждении проекта, значит, по всей
видимости, есть возможность улучшения иерархии. Заметим, что
подразумевается, что анализ иерархии классов лучше проводить не в
одиночку. Если вы оказались в таком положении, когда не с кем
обсудить проект, хорошим выходом будет попытаться составить учебное
описание системы, используя имена классов.
Достарыңызбен бөлісу: |