В одном из предыдущих постов я обещал рассказать об интересном примере использования множественного наследования. Настало время сдержать обещание.

Предположим у нас есть класс графа:

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 ). И через эту функцию рулить размерами диалога/окна. Вот и все. Просто? Да. Элегантно? Ещё бы. К сожалению, самые лучшие идеи приходят к нам уже после релиза )).

Ссылки по теме:
Старые парадигмы о главном