На этом шаге рассмотрим множественное наследование.
При одиночном наследовании каждый подкласс имеет только один суперкласс. Однако, как указали Влиссидес (Vlissides) и Линтон (Linton), несмотря на очевидную полезность, "одиночное наследование часто вынуждает программиста выбирать наследование от одного из двух одинаково привлекательных классов. Это ограничивает применение предопределенных классов, поэтому часто приходится дублировать код. Например, нельзя создать графический элемент, который был бы одновременно окружностью и рисунком; приходится наследовать свойства либо одного, либо другого класса, добавляя необходимые функциональные возможности отвергнутого класса" [Vlissides, J., and Linton, M. 1988. Applying Object-Oriented Design to Structured Graphics. Proceedings of USENIX C++ Conference. Berkeley, CA: USENIX Association, p. 93.].
Предположим, необходимо организовать учет различных активов — сберегательных счетов, недвижимости, акций и облигаций. Банки обычно управляют двумя видами активов: текущими и сберегательными счетами. Таким образом, оба этих счета можно считать разновидностью банковских счетов, которые, в свою очередь, являются разновидностью активов. Акции и облигации отличаются от банковских счетов — они представляют собой разновидности ценных бумаг, которые также относятся к активам.
С другой стороны, существует много других удобных классификаций сберегательных счетов, недвижимости, акций и облигаций. Например, в некоторых ситуациях полезно отличать активы, которые можно застраховать, например, недвижимость или определенные банковские счета. В других ситуациях иногда полезно уметь идентифицировать активы, приносящие дивиденды или процентный доход, такие как сберегательные и текущие счета, а также некоторые виды акций и облигаций.
К сожалению, выразительные средства одиночного наследования не позволяют достаточно полно описать это разнообразие отношений, поэтому необходимо использовать множественное наследование. Этот признак является критерием для использования множественного наследования. Если листья диаграммы классов, имеющих ортогональные свойства (например, активы, которые можно застраховать, и активы, приносящие процентный доход), можно объединить в пересекающиеся множества, то при одиночном наследовании не существует ни одного промежуточного класса, который обладал бы указанными свойствами, не разрушая абстракцию листовых классов из-за включения в них свойств, которыми они не должны обладать. Множественное наследование может исправить эту ситуацию, поскольку оно позволяет смешивать только желательные свойства.
Такая структура классов показана на рис. 1. Класс Security — разновидность классов Active и InterestBearingItem. Аналогично, класс BankAccount является разновидностью классов Asset, InsurableItem и InterestBearingItem.
Рис.1. Множественное наследование
Проектирование подходящей структуры классов, использующей наследование, и особенно множественное наследование, — трудная задача. Задача проектирования подходящей структуры классов, использующей наследование, и особенно множественное наследование, решается методом последовательных приближений. Множественное наследование порождает две проблемы: как разрешить конфликты имен между разными суперклассами и что делать с повторным наследованием.
Конфликт имен возможен, если в нескольких суперклассах используется одно и то же имя, обозначающее определенный элемент интерфейса класса. Например, предположим, что классы Insurableltem и Asset содержат атрибут presentValue, обозначающий текущую стоимость актива. Поскольку класс RealEstate является наследником обоих этих классов, неясно, что означает наследование двух операций с одним и тем же именем. На самом деле, эта проблема является основной для множественного наследования: конфликт имен может внести двусмысленность в поведение подкласса, имеющего несколько суперклассов.
Существует три подхода к разрешению конфликтов имен:
Вторая проблема — повторное наследование. Мейер (Meyer) считает, что при использовании множественного наследования возникает одна тонкость: неясно, что делать, если один и тот же класс является наследником другого класса по нескольким линиям. Если язык программирования допускает множественное наследование, то рано или поздно кто-нибудь напишет класс D, являющийся наследником классов В и С, каждый из которых, в свою очередь, будет наследником класса А, или же возникнет другая ситуация, в которой класс D будет двойным (или даже многократным) наследником класса А. Эта ситуация называется повторным наследованием и требует большой осторожности [Meyer, B. 1988. Object-Oriented Software Construction. New York, NY: Prentice Hall, p. 274.].
Предположим, например, что проектировщик принял неверное решение, назначив класс MutualFund (Совместный фонд) наследником двух классов Stock и Bond (см. рис. 1). В результате возникло повторное наследование от класса Security, являющегося суперклассом по отношению к классам Stock и Bond.
Языки программирования используют разные способы решения этой проблемы. Существует три способа решения проблемы повторного наследования:
Множественное наследование породило особую категорию классов — примеси (mixins). Идея примесей возникла в среде языка Flavors: он допускал комбинирование (смешение) небольших классов при разработке классов с более сложным поведением. С синтаксической точки зрения, примесь идентична обычному классу, но они имеют разные цели. Примесь предназначена лишь для добавления функций в другие классы — никто никогда не создает экземпляры примесей [Hendler, J. October 1986. Enhancement for Multiple Inheritance. SIGPLAN Notices vol. 21(10), p. 100.].
На рис. 1 классы InsurableItem и InterestBearingItem являются примесями. Ни один из них не может существовать сам по себе; они используются для расширения свойств другого класса. Таким образом, примесь — это класс, обладающий отдельным, узкоспециализированным поведением и предназначенный для расширения функциональных возможностей другого класса с помощью механизма наследования. Функциональное свойство примеси, как правило, полностью ортогонально свойствам класса, с которым она комбинируется. Класс, созданный главным образом из примесей, называется агрегатным (aggregate class).
На следующем шаге рассмотрим отношения агрегации и зависимости между классами.