Примеры иерархии: множественное наследование. В предыдущем примере рассматривалось одиночное наследование, когда подкласс FruitGrowingPlan был создан только из одного суперкласса GrowingPlan. В ряде случаев полезно реализовать наследование от нескольких суперклассов. Предположим, что нужно определить класс, представляющий разновидности растений.
class Plant { public:
Plant(char* name, char* species); virtual ~Plant(); void setDatePlanted(Day); virtual establishGrowingConditions(const Condition&); const char* name() const; const char* species() const; Day datePlantedt) const;
protected:
char* repName; char* repSpecies; Day repPlanted;
private: ... };
Каждый экземпляр класса plant будет содержать имя, вид и дату посадки. Кроме того, для каждого вида растений можно задавать особые оптимальные условия выращивания. Мы хотим, чтобы эта функция переопределялась подклассами, поэтому она объявлена виртуальной при реализации в C++. Три параметра объявлены как защищенные, то есть они будут доступны и классу, и подклассам (закрытая часть спецификации доступна только самому классу).
Изучая предметную область, мы приходим к выводу, что различные группы культивируемых растений - цветы, фрукты и овощи, - имеют свои особые свойства, существенные для технологии их выращивания. Например, для цветов важно знать времена цветения и созревания семян. Аналогично, время сбора урожая важно для абстракций фруктов и овощей. Создадим два новых класса - цветы (Flower) и фрукты-овощи (FruitVegetable); они оба наследуют от класса Plant. Однако некоторые цветочные растения имеют плоды! Для этой абстракции придется создать третий класс, FlowerFruitVegetable, который будет наследовать от классов Flower и FruitVegetablePlant.
Чтобы не было избыточности, в данном случае очень пригодится множественное наследование. Сначала давайте опишем отдельно цветы и фрукты-овощи.
class FlowerMixin { public:
FlowerMixin(Day timeToFlower, Day timeToSeed); virtual ~FlowerMixin(); Day timeToFlower() const; Day timeToSeed() const;
protected: ... };
class FruitVegetableMixin { public:
FruitVegetableMixin(Day timeToHarvest); virtual ~FruitVegetableMixin(); Day timeToHarvest() const;
protected: ... };
Мы намеренно описали эти два класса без наследования. Они ни от кого не наследуют и специально предназначены для того, чтобы их подмешивали (откуда и имя Mixin) к другим классам. Например, опишем розу:
class Rose : public Plant, public FlowerMixin...
А вот морковь:
class Carrot : public Plant, public FruiteVegetableMixin {};
В обоих случаях классы наследуют от двух суперклассов: экземпляры подкласса Rose включают структуру и поведение как из класса Plant, так и из класса FlowerMixin. И вот теперь определим вишню, у которой товаром являются как цветы, так и плоды:
class Cherry : public Plant, public FlowerMixin, FruitVegetableMixin...
Множественное наследование - вещь нехитрая, но оно осложняет реализацию языков программирования. Есть две проблемы - конфликты имен между различными суперклассами и повторное наследование. Первый случай, это когда в двух или большем числе суперклассов определено поле или операция с одинаковым именем. В C++ этот вид конфликта должен быть явно разрешен вручную, а в Smalltalk берется то, которое встречается первым. Повторное наследование, это когда класс наследует двум классам, а они порознь наследуют одному и тому же четвертому. Получается ромбическая структура наследования и надо решить, должен ли самый нижний класс получить одну или две отдельные копии самого верхнего класса? В некоторых языках повторное наследование запрещено, в других конфликт решается "волевым порядком", а в C++ это оставляется на усмотрение программиста. Виртуальные базовые классы используются для запрещения дублирования повторяющихся структур, в противном случае в подклассе появятся копии полей и функций и потребуется явное указание происхождения каждой из копий.