Шаг 14 - Двойная диспетчеризация. Продолжение

В Шаге 4 мы говорили о двойной диспетчеризации. Она очень хорошо подходит при необходимости отображения одних объектов посредством других, но не только; она в общем применима, когда Вам нужно обрабатывать попарные (и более) взаимодействия объектов двух и более разных классов. Получается этакая табличка, на осях которой нарисованы классы, а в ячейках - функции их взаимодействия. Количество функций равно произведению столбцов и строк этой таблички. А если диспетчеризация тройная или выше? Тогда еще умножаем на количество слоев, и дальше и дальше...

Как бы упростить жизнь? А вот так - если взаимодействие двух объектов дает один результат, пусть этим и занимается одна функция. Попробуем перевести на человеческий язык:

Пусть есть класс CTitanic и класс CIceberg. Их карма в том, чтобы столкнуться. Четыре варианта взаимодействия: Столкновение двух Ctitanic не ведет ни к чему, если вообще возможно, двух CIceberg - у них там свои дела, столкновение CTitanic и CIceberg, как известно, к семи Оскарам, и столкновение CIceberg и CTitanic - к тому же самому. То есть функций всего три. Определим взаимодействие этих классов как функцию hit(). Вот код:

#include <iostream.h>
// Форвардные объявления
class CTitanic;
class CIceberg;
class CFloating;

// Абстрактный базовый класс
class CFloating
{
public:
	virtual void hit(CIceberg&)=0;
	virtual void hit(CTitanic&)=0;
public:
	virtual void hit(CFloating&)=0;
};

// Класс айсберга
class CIceberg
{
public:
	virtual void hit(CIceberg&); 
	virtual void hit(CTitanic&);
public:
	virtual void hit(CFloating&);
};

// Первая диспетчерская функция
void CIceberg::hit(CFloating& _co) {
	_co.hit(*this);
}

// Две реализации взаимодействия
void CIceberg::hit(CIceberg& _ci) {
	cout << "ci+ci" << endl;
}

void CIceberg::hit(CTitanic& _ct) {
	cout << "ci+co" << endl;
}

// Класс Титаника
class CTitanic
{
public:
	virtual void hit(CIceberg&);
	virtual void hit(CTitanic&);
public:
	virtual void hit(CFloating&);
};

// Еще одна диспетчерская функция
void CTitanic::hit(CFloating& _co)
{
	_co.hit(*this);
}

// А вот эта функция могла бы быть реализацией
// но мы ее тоже делаем диспетчерской;
// в этом фрагменте диспетчеризация тройная.
void CTitanic::hit(CIceberg& _ci)
{
//	cout << "co+ci" << endl;   Это могла быть реализация
	_ci.hit(*this);
}
void CTitanic::hit(CTitanic& _ct)
{
	cout << "co+co" << endl;
}
// проверим по быстрому, как работает
int main ()
{
	CIceberg i1;
	CTitanic t1;
	CIceberg i2;
	CTitanic t2;
	i1.hit(t1) ;
	i1.hit(i2) ;
	t1.hit(i1) ;
	t1.hit(t2) ;
	return 0;
}

Пояснения по коду: взаимодействующие классы надобно определить от одного общего предка, коли они уж плавают и могут друг об друга биться, так и запишем - все варианты взаимодействия должны быть чистыми виртуальными функциями.

В общем, количество действительных реализаций функций уменьшается как раз на количество совпадающих. Не так уж и плохо.

Есть еще способы уменьшить их количество, основанные на преобразованиях классов - неявных или через конструкторы. Я правда не знаю, что раньше может запутать - количество диспетчерских функций или неявные преобразования; тут, пожалуй, можно только порадоваться появлению в стандарте ограничивающего модификатора explicit, который подавляет неявные вызовы конструкторов.

Увы, двойная диспетчеризация в C++ всегда громоздкая и неудобная вещь, и вряд ли будет другой. Если мы добавляем новые классы в диспетчеризацию, приходится переписывать ранее написанные классы; все классы имеют доступ к функциям друг друга или функции должны быть открытыми.

Это - плата за отсутствие в C++ функций, виртуальных по отношению к 2 и более классам.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 13.04.2001