9.3. Противопоставление композиции и наследования
Проиллюстрировав два механизма многократного использования программного
обеспечения и увидев, что они оба применимы для реализации множеств, мы можем
прокомментировать некоторые недостатки и преимущества двух подходов:
• Композиция более проста. Ее преимущество заключается в том, что она ясно
показывает, какие точно операции будут выполняться над конкретной структурой
данных. При взгляде на описание абстракции данных Set становится очевидно, что
для типа данных предусмотрены только операции добавления элемента, проверки
на наличие элемента и определение числа элементов в наборе. Это справедливо
независимо от того, какие операции определены для списков.
• При наследовании операции новой абстракции данных являются надмножеством
операций исходной структуры данных. Таким образом, чтобы точно знать, какие
операции разрешены для новой структуры, программист должен рассмотреть
объявление исходной структуры. Например, изучение описания класса Set не
показывает сразу же, что проверка на наличие элемента (метод includes) разрешена
для множеств. Только из рассмотрения описанной ранее абстракции данных List
видно, что имеется еще целый набор допустимых операций. Трудность состоит в
следующем: чтобы понять класс, сконструированный с помощью наследования,
программист должен постоянно переключаться «взад-вперед» между двумя (или
более) описаниями классов. Она известна как проблема «вверх-вниз» («йо-йо»)
[Taenzer 1989].
• С другой стороны, лаконичность абстракции данных, созданной с помощью
наследования, является преимуществом. Используя наследование, не обязательно
писать весь код для доступа к функциям базового класса. По этой причине
реализации с использованием наследования (как это было в нашем случае)
значительно меньше по объему, если сравнить их с композицией. Наследование
также часто обеспечивает большую функциональность. Например, применение
наследования в нашем случае делает доступным для множеств не только проверку
include, но и функцию remove.
• Наследование не запрещает пользователям манипулировать новыми структурами
через вызовы методов родительского класса, даже если эти методы не вполне
подходят под идеологию потомка. Например, когда мы использовали наследование
для получения множеств Set из списков List, то ничто не мешало пользователям
добавлять новые элементы к множеству, вызывая унаследованный от класса List
метод addToFront.
• При композиции тот факт, что класс List используется для хранения наших
множеств, — просто деталь реализации. С этой техникой было бы легко заново
реализовать класс, чтобы извлечь пользу из применения других методов
(например, таких, как хэш-таблицы) с минимальным воздействием на
пользователей абстракции данных Set. Если пользователь рассчитывает на тот
факт, что абстракция Set — это просто уточненная форма абстракции List, то такие
изменения было бы трудно реализовать.
• Наследование позволяет нам использовать новую абстракцию как аргумент
существующей полиморфной функции. Мы будем исследовать эту возможность
более детально в главе 14. Так как композиция не подразумевает соблюдение
принципа подстановки, она обычно устраняет полиморфизм.
• О степени понятности кода судить трудно. Наследование имеет преимущество в
краткости кода, но не протокола. При композиции код класса, хотя он и
оказывается длиннее, — это все, что должен понять другой программист, чтобы
использовать абстракцию. Человек, столкнувшийся с необходимостью разобраться
PDF created with pdfFactory Pro trial version www.pdffactory.com