Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [ 28 ] 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

Можно ли разбить такой класс на составляющие, не прибегая к сложностям создания производных классов или делегирования? Конечно можно, если воспользоваться технологией мудрых указателей. Достаточно создать для одного объекта несколько указателей, каждый из которых отвечает за некоторый аспект деятельности этого объекта.

class ViewEvents { private:

View* view; public:

Функции, относящиеся к обработке событий

class ViewDrawing { private:

View* view; public:

Функции, относящиеся к графическому выводу

И т.д.

Каждый из этих мудрых указателей воспроизводит интерфейс к некоторому подмножеству функций класса View и перенаправляет вызовы функциям-прототипам объекта вида. Сам объект вида может быть устроен как угодно: на основе одиночного и множественного наследования, делегирования в комплексе объектов или в форме одного гигантского конгломерата; клиентов это волновать не должно. Я называю такие интерфейсные указатели, ограничивающие клиента подмножеством полного интерфейса, гранями (facets).

Эта базовая идея укоренилась как минимум в одной коммерческой технологии - компонентной модели объекта (СОМ, Component Object Model) компании Microsoft, которая называет эти указатели интерфейсами. Один из мелких разработчиков, компания Quasar Knowledge Systems, предложила аналогичную идею для объектов SmallTalk и назвала такие указатели комплексами (suites). Как бы они ни назывались, этой идее суждено стать одной из важнейших идиом дизайна объектно-ориентированного программирования будущего, поскольку она обладает повышенной гибкостью и модульностью по сравнению с функциональным делением на основе наследования и делегирования.

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

Преобразование указываемого объекта в грань

Итак, вы хотите получить грань по имеющемуся указываемому объекту. Для этого существует много способов, однако наиболее соответствующий стилю C++ заключается в использовании операторов преобразования.

class View { public:

operator ViewEvents() { return new ViewEvents(this); } operator ViewDrawing() { return new ViewDrawing(this); }

Другой вариант - разрешить пользователю напрямую использовать конструкторы класса грани: ViewEvents ve(aView);

Об их достоинствах и недостатках можно спорить долго, но лично я предпочитаю первый способ по причинам, о которых мы вскоре поговорим. Существует еще один способ, который тоже заслуживает упоминания - присвоить каждому типу грани уникальный идентификатор и затем создать единую, многоцелевую функцию генерации граней для всех классов. Такая функция получает идентификатор типа грани в качестве аргумента и возвращает грань, если она поддерживается объектом, и NULL - в



противном случае. Те из вас, кому приходилось пользоваться технологиями СОМ и OLE компании Microsoft, узнают знакомую функцию Querylnterface, поддерживаемую всеми объектами.

Кристаллы

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

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 базовым для всех остальных. Такой вариант возможен, но тогда исчезнут некоторые важные преимущества. Приведенная выше модель является абсолютно плоской; между гранями не существует отношений наследования. Благодаря этому возникает огромная степень свободы в реализации - для поддержания этих интерфейсов можно использовать наследование, делегирование и агрегирование (внедренные переменные класса). Все это с лихвой окупает одно лишнее преобразование типа.

Вариации на тему граней

Грани можно реализовать несколькими способами. В совокупности они образуют надмножество тех возможностей, которые в 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();



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [ 28 ] 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82