Поскольку valueDelimOpen и valueDelimClose – виртуальные функции, возвращаемый результат theName зависит не только от PersonInfo, но и от классов, производных от него.
Для разработчика CPerson это хорошая новость, потому что, внимательно просматривая документацию по функциям печати из класса IPerson, вы обнаруживаете, что функции name и birthDate должны возвращать неформатированные значения, то есть без добавления разделителей. Другими словами, если человека зовут Homer, то вызов функции name должен возвращать «Homer», а не «[Homer]».
Взаимосвязь между CPerson и PersonInfo можно описать так: PersonInfo упрощает реализацию некоторых функций CPerson. И это все! Стало быть, речь идет об отношении «реализован посредством», и, как мы знаем, такое отношение можно представить двумя способами: с помощью композиции (см. правило 38) или закрытого наследования (см. правило 39). В правиле 39 отмечено, что композиция в общем случае более предпочтительна, но если нужно переопределять виртуальные функции, то требуется наследование. В данном случае CPerson должен переопределить valueDelimOpen и valueDelimClose – задача, которая с помощью композиции не решается. Самое очевидное решение – применить закрытое наследование CPerson от PersonInfo, хотя, как объясняется в правиле 39, это потребует несколько больше работы. Можно также при реализации CPerson воспользоваться сочетанием композиции и наследования с целью переопределения виртуальных функций PersonInfo. Но мы остановимся просто на закрытом наследовании.
Однако CPerson также должен реализовать интерфейс IPerson, а для этого требуется открытое наследование. Вот мы и пришли к множественному наследованию: сочетанию открытого наследования интерфейса с закрытым наследованием реализации:
class IPerson { // класс описывает интерфейс,
public: // который должен быть реализован
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
class DatabaseID {...}; // используется далее;
// детали не существенны
class PersonInfo { // в этом классе имеет функции,
public: // помогающие при реализации
explicit PersonInfo(DatabaseID pid) // интерфейса IPerson
virtual ~PersonInfo();
virtual const char *theName() const;
virtual const char *theBirthDate() const;
virtual const char *valeDelimOpen() const;
virtual const char *valeDelimClose() const;
...
};
class CPerson: public IPerson, private PersonInfo { // используется
public: // множественное
explicit CPerson(DatabaseID pid): PersonInfo(pid) {} // наследование
virtual std::string name() const // реализации
{ return PersonInfo::theName();} // функций-членов
// из интерфейса
// IPerson
virtual std::string birthDate() const
{ return PersonInfo::theBirthDate();}
private: // переопределения
const char * valeDelimOpen() const { return “”;} // унаследованных
const char * valeDelimClose() const { return “”;} // виртуальных
}; // функций,
// возвращающих
// строки-разделители
В нотации UML это решение выглядит так:
Рассмотренный пример показывает, что множественное наследование может быть и удобным, и понятным.