Предположим у нас есть класс графа:
class Graph{ public: // тут идут методы, отвечающие за инициализацию объектов // ну а тут понеслись алгоритмы работы с графами void FindSomething1( void ); void FindSomething2( void ); // и так далее private: // тут идут поля, матрица смежности, например, или количество вершин в графе };
Теперь внимание — класс реализует только один способ представления графа. А если мы хоитм чтобы у нас было несколько способов представления графа? В каждом классе плодить FindSomething1, FindSomething2 и прочие алгоритмы? Очевидно, есть архитектурные решения, позволяющие избежать лишней работы. Одним из таких решений является множественное наследование, применённое специальным образом. Вот как это делается:
В базовый класс выносим все алгоритмы, но не данные:
class GraphAlgorithms{ public: void FindSomething1( void ); void FindSomething2( void ); // и так далее // функция возвращает количество вершин в графе virtual int CountOfVertexes( void ) = 0; // * // функция возвращает true если между вершинами i и j есть ребро virtual bool EdgeExists( std::size_t i , std::size_t j ) = 0; // ** };
Фишка в том что хотя объект класса GraphAlgorithms создать нельзя, т.к. имеются чисто-виртуальные функции, однако функции не являющиеся чисто виртуальными можно определять, причем во время этого определения можно активно пользоваться чисто-виртуальными функциями ** и *. Например:
void GraphAlgorithms::FindSomething1( void ) { if( CountOfVertexes() != 0 )// компилятор на эту строчку нам ничего не скажет )) { // делаем свои дела } else { // обрабатываем ошибочную ситуацию } }
Теперь можно эти алгоритмы навесить на любое представление графа просто унаследовавшись от GraphAlgorithms и определить наши чисто виртуальные функции:
class Graph : public GraphAlgorithms{ public: // функция возвращает количество вершин в графе virtual int CountOfVertexes( void ); // функция возвращает true если между вершинами i и j есть ребро virtual bool EdgeExists( std::size_t i , std::size_t j ); private: // тут идут поля класса Graph и закрытые методы };
Можно разделить алгоритмы работы с графом по нескольким классам (правда в этом случае придется использовать виртуальное наследование, сами подумайте где):
class Graph : public GraphAlgorithms1 , public GraphAlgorithms2 , public GraphAlgorithms3{ // ... };
Это на 100% тот случай когда наследование используется к месту и не приводит к лишнему геморрою. Здесь множественное наследование позволяет легко расширить функциональность класса Graph, практически не предъявляя никаких требований к этому классу + мы получили разделение алгоритмов и данных со всеми вытекающими отсюда бонусами.
Ладно, бог с ними с графами, вот реальный пример как можно было бы сделать, но как не было сделано: ETSLayout. Это, кстати, один из лучших классов в своем роде. Однако, для его использвания клиентский код должен наследоваться от класса ETSDialog (который, к слову, является потомком класса CDialog), а что если я уже использую в своей программе какой-нибудь хитровыделанный потомок CDialog? Что мне тогда делать? Отказываться от какого-то из классов в пользу другого? Или перелопачивать кучу кода? Этой проблемы не было бы, если бы автор лэйаута реализовал свою задумку как я описал выше — надо было бы просто сделать чисто виртуальную функцию CDialog * GetChild( void ), которая будет преопределена в производном классе и будет состоять из одной строчки — return( this ); А может вполне хватило бы CWnd * GetChild( void ). И через эту функцию рулить размерами диалога/окна. Вот и все. Просто? Да. Элегантно? Ещё бы. К сожалению, самые лучшие идеи приходят к нам уже после релиза )).
Ссылки по теме:
Старые парадигмы о главном