Кристаллы
Если у вас имеется одна грань и вы хотите получить другую грань того же объекта, наиболее прямолинейный подход также заключается во включении операторов преобразования в грань. Упрощенный подход выглядит так:
class ViewEvents {
private:
View* view;
public:
operator ViewDrawing() { return ViewDrawing(*view); }
// И т.д. для других граней
};
В этом маленьком С++-изме работа поручается операторной функции operator ViewDrawing() целевого вида. При малом количестве граней такое решение вполне приемлемо. С ростом количества граней число операторов преобразования возрастает в квадратичной зависимости, поскольку каждая грань должна преобразовывать ко всем остальным. Следующая модификация возвращает задачу к порядку n, где n - количество граней. Продолжая свою откровенно слабую метафору, я называю объект, который собирает и выдает грани, кристаллом (gemstone).
class View;
class ViewEvents;
class ViewDrawing;
class ViewGemstone {
private:
View* view;
public:
ViewGemstone(View* v) : view(v) {}
bool operator!() { return view == NULL; }
operator ViewEvents();
operator ViewDrawing();
// И т.д.
};
class ViewEvents {
friend class ViewGemstone;
private:
View* view;
ViewEvents(View* v) : view(v) {}
public:
bool operator!() { return view == NULL; }
operator ViewGemstone();
};
class ViewDrawing {
friend class ViewGemstone;
private:
View* view;
ViewDrawing(View* v) : view(v) {}
public:
bool operator!() { return view == NULL; }
operator ViewGemstone();
};
У нас есть один объект, кристалл, который умеет генерировать все грани; каждая грань, в свою очередь, знает, как найти кристалл. Кристалл является единственным объектом, который может создавать грани, так как последние имеют закрытые конструкторы и дружат с кристаллом. Концепция кристалла чрезвычайно гибка - он может быть самостоятельным объектом, абстрактным базовым классом объекта и даже одной из граней.
С первого взгляда кажется, что такое решение создает излишние неудобства для пользователя, которому приходится выполнять два последовательных преобразования типа. Наверное, кому-нибудь захочется сделать класс ViewGemstone базовым для всех остальных. Такой вариант возможен, но тогда исчезнут некоторые важные преимущества. Приведенная выше модель является абсолютно плоской; между гранями не существует отношений наследования. Благодаря этому возникает огромная степень свободы в реализации - для поддержания этих интерфейсов можно использовать наследование, делегирование и агрегирование (внедренные переменные класса). Все это с лихвой
окупает одно лишнее преобразование типа.