Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4087 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 6:

Используйте наследование правильно

Как разрабатываются структуры наследования

При чтении книги или учебной статьи по ОО-методу или при обнаружении библиотеки классов с уже спроектированной иерархией наследования авторы не всегда говорят о том, как они пришли к конечному результату. Что же следует делать при проектировании собственных структур?

Специализация и абстракция

Произвольно или нет, но многие учебные презентации создают впечатление, что структуру наследования следует проектировать от наиболее общего (верхней ее части) к более специфическим частям (листьям). В частности, это происходит потому, что лучший способ описать существующую структуру - это идти от общего к частному, от фигур к замкнутым фигурам, затем к многоугольникам, прямоугольникам, квадратам. Но лучший способ описания структуры вовсе не означает, что он является и лучшим способом ее создания.

Подобный комментарий, сделанный Майклом Джексоном, упоминался при рассмотрении проектирования сверху вниз.

В идеальном мире, населенном совершенными людьми, мы бы сразу же обнаруживали правильные абстракции, выводили бы из них категории, их подкатегории и так далее. В реальном мире, однако, мы часто вначале обнаруживаем специальный случай и лишь потом открываем общую абстракцию.

Во многих ситуациях абстракция не является уникальной; как лучше обобщить некоторое понятие, зависит от того, что вы и ваши клиенты хотите сделать с этим понятием и его вариантами. Рассмотрим, например, понятие, неоднократно встречающееся в наших рассмотрениях, - точку в двумерном пространстве. Возможны по меньшей мере четыре обобщения:

  • точки в пространстве произвольной размерности, приводящие к наследственной структуре, где братьями класса POINT будут классы POINT_3D и так далее;
  • геометрические фигуры - другими классами структуры могут быть FIGURE, RECTANGLE, CIRCLE и так далее;
  • многоугольники - с такими классами, как QUADRANGLE (четыре вершины), TRIANGLE (три вершины) и SEGMENT (две вершины), POINT является специальным случаем, имеющим ровно одну вершину;
  • объекты, полностью определяемые двумя координатами - другими кандидатами являются комплексные числа и двумерные векторы COMPLEX и VECTOR_2D.

Интуитивно некоторые из этих обобщений кажутся более приемлемыми, чем другие, но невозможно со всей определенностью выбрать наилучшее. Ответ зависит от потребностей. Потому предусмотрительный и осторожный процесс, в котором абстракция создается с некоторым опозданием, чтобы точно убедиться в правильном выборе пути обобщения, может быть предпочтительнее скорых и быстрых решений, приводящих к непроверенной абстракции.

Произвольность классификации

Пример класса POINT типичен. Когда сталкиваешься с двумя конкурирующими классификациями из некоторого множества абстракций, то часто можно привести разумные аргументы в пользу одной из них. Значительно реже кто-то может утверждать, что данная структура является наилучшей из всех возможных.

Эта ситуация не является спецификой разработки ПО. Классификация Линнея не является универсально приемлемой или непреложной. У нее есть соперники, один из которых вместо традиционного эволюционного критерия использует другой, более индуктивный, основанный на ДНК-анализе и приводящий к совершенно другим результатам. Есть зоологи, для которых умение (неумение) птиц летать является важным таксономическим признаком, но официальная классификация с этим не соглашается.

Индукция и дедукция

При проектировании семейства классов программной системы подходящим процессом является комбинация дедукции и индукции, специализации и обобщения. Иногда вы начинаете с абстракции, а затем выводите частные случаи, иногда вы обнаруживает полезный класс, а затем осознаете существование более общей абстрактной концепции.

Если вы обнаруживаете абстракцию только после распознавания конкретного, возможно, с вами все в порядке. Вы просто используете нормальный подход к классификации. С приобретением опыта и появлением интуиции возрастет доля априорных решений. Но апостериорная составляющая всегда останется.

Разнообразие абстракции

Этот принцип атавизма - один из наиболее удивительных из всех 
		атрибутов наследования.
						Чарльз Дарвин

Две формы апостериорного конструирования родителя являются общими и полезными.

Абстрагирование представляет позднее обнаружение концепции высшего уровня. Вы находите класс B, покрывающий полезное понятие, но чей разработчик не обнаружил, что это фактически специальный случай общего понятия A, для которого оправдана наследственная связь:

Абстракция

Рис. 6.17. Абстракция

То, что это понимание не пришло сразу, другими словами, то, что B был построен без учета A, - не является причиной отказа от наследования в этом случае. Сразу же при обнаружении необходимости A вы можете, а в большинстве случаев должны написать этот класс и адаптировать B как его наследника. Это не столь хорошо, как написать раньше A, но лучше, чем не написать вовсе.

Факторизация возникает в случае обнаружения того, что два класса E и F фактически представляют варианты одного и того же понятия:

Факторизация

Рис. 6.18. Факторизация

Если вы с запозданием обнаружили эту общность, то шаг обобщения позволит добавить общий родительский класс D. Здесь снова предпочтительнее построить иерархию сразу же, но лучше позже, чем никогда.

Независимость клиента

Абстрагирование и факторизация могут во многих случаях выполняться без негативных последствий для существующих клиентов (приложение принципа Открыт-Закрыт). Это свойство является результатом использования скрытия информации. Рассмотрим снова предшествующие схематические случаи, но с типичным клиентским классом X, показанным на рисунке:

Абстракция, факторизация и клиенты

Рис. 6.19. Абстракция, факторизация и клиенты

Когда B абстрагируется в A, или компоненты E факторизуются с компонентами F в D, класс X, представляющий клиента B или E (на рисунке он клиент обоих классов) в большинстве случаев не заметит никаких изменений. Включение класса в схему наследования не оказывает влияния на его клиентов, если они применяют компоненты класса на сущностях соответствующего типа. Другими словами, если X использует B и E как поставщиков по схеме:

b1: B; e1: E
...
b1.some_feature_of_B
...
e1.some_feature_of_E

то X не заметит, что B или E обрели родителей в результате абстрагирования или факторизации.

Совершенствование уровня абстракции

Абстрагирование и факторизация являются типичным процессом постоянных улучшений, характерных при успешном конструировании ОО-ПО. На основании своего опыта могу сказать, что это один из наиболее воодушевляющих аспектов практики метода: знание того, что при всей невозможности достичь совершенства с самого начала вам дается возможность улучшать ваш проект постоянно, пока он не будет удовлетворять каждого.

В группе разработчиков, правильно применяющих ОО-метод, это регулярное совершенствование уровня абстракции ПО и, как следствие, его качества, заметно ощущается членами команды и служит источником постоянной мотивации.