99
public:
bool operator!() { return view == NULL; }
operator
ViewGemstone();
};
У нас есть один объект, кристалл, который умеет генерировать все грани; каждая грань, в свою
очередь, знает, как найти кристалл. Кристалл является единственным объектом, который может
создавать грани, так как последние имеют закрытые конструкторы и дружат с кристаллом. Концепция
кристалла чрезвычайно гибка — он может быть самостоятельным объектом, абстрактным базовым
классом объекта и даже одной из граней.
С первого взгляда кажется, что такое решение создает излишние неудобства для пользователя,
которому приходится выполнять два последовательных преобразования типа. Наверное, кому-нибудь
захочется сделать класс
ViewGemstone
базовым для всех остальных. Такой вариант возможен, но
тогда исчезнут некоторые важные преимущества. Приведенная выше модель является абсолютно
плоской; между гранями не существует отношений наследования. Благодаря этому возникает огромная
степень свободы в реализации — для поддержания этих интерфейсов можно использовать
наследование, делегирование и агрегирование (внедренные переменные класса). Все это с лихвой
окупает одно лишнее преобразование типа.
Вариации на тему граней
Грани можно реализовать несколькими способами. В совокупности они образуют надмножество тех
возможностей, которые в C++ поддерживаются с помощью наследования и переменных класса.
Грани — множества подфункций
Самая простая форма грани — та, которая предоставляет интерфейс к подмножеству функций
указываемого объекта.
// В файле Pointee.h
class Pointee;
class Facet {
friend class PointeeGemstone;
private:
Pointee*
pointee;
Facet(Pointee* p) : pointee(p) {}
public:
void
Fn1();
int
Fn2();
void
Fn17();
};
class PointeeGemstone {
private:
Pointee*
pointee;
public:
PointeeGemstone(Pointee* p) : pointee(p) {}
Operator
Facet();
};
// В файле Pointee.cpp
class Pointee {
public:
void
Fn1();
100
int
Fn2();
void
Fn3();
char
Fn4();
//
И т.д.
void
Fn17();
};
Здесь грань просто отбрасывает все функции, которые не входят в ее компетенцию. Клиент имеет дело
с «объектом», который намного легче всего указываемого объекта, но за кулисами все равно прячется
полный объект.
Грани — переменные класса
Грань может представлять собой интерфейсный указатель на переменную класса. Это позволяет
многократно использовать грань в различных кристаллах или для организации интерфейса к
отдельному экземпляру. Если указываемый объект имеет переменную класса
Bar
, грань может
представлять собой простой интерфейсный указатель на
Bar
.
// В файле Pointee.h
class BarFacet {
private:
Bar*
bar;
public:
BarFacet(Bar* b) : bar(b) {}
//
Интерфейсы к функциям класса Bar
};
class PointeeGemstone {
private:
Pointee*
p;
public:
operator
BarFacet();
//
И т.д.
};
// В файле Pointee.cpp
class Pointee {
friend class PointeeGemstone;
private:
Bar
bar;
//
Внедренная переменная класса Pointee
public:
//
И т.д.
};
PointeeGemstone::operator BarFacet()
{
return
BarFacet(&p->Bar);
//
Грань переменной
}
Все прекрасно работает, если вам хватает относительно простых правил согласованности C++.
Вероятно, в более общем случае стоит воспользоваться приемами, описанными далее, в разделе
«Обеспечение согласованности». В частности, одна из проблем такого упрощенного подхода
Do'stlaringiz bilan baham: |