Анимация
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

Шаблон Visitor

в этой главе обсуждаются обобщенные компоненты, использующие щаблон проектирования visitor (Gamma et al., 1995). Это мощный щаблон проектирования, измешпо-щий зависимость между проектными решениями, принятыми при разработке класса.

Шаблон visitor (Инспектор) обеспечивает удивительную гибкость: в иерархию классов можно добавлять виртуальные функции, не прибегая к повторной компиляции ни этих функций, ни существующих клиентских кодов. Однако эта гибкость достигается за счет определенных жертв: в иерархию теперь невозможно добавить новый терминальный класс (leaf class), не повторив компиляцию всей иерархии и ее клиентов. Следовательно, область применения шаблона visitor ограничивается только очень устойчивыми иерархиями (в которые редко добавляются новые классы) и программами, в которые часто приходится добавлять новые виртуальные функции.

Шаблон Visitor противоречит программистской интуиции. Следовательно, для его успешного применения нужны тщательная реализация и строгая дисциплина. В главе описывается обобщенная реализация шаблона visitor, оставляющая прикладному программисту очень мало работы.

В главе обсуждаются следующие темы.

• Принцип работы шаблона visitor.

• Когда следует применять шаблон visitor и, что не менее важно, когда его применять не следует.

• Базовая реализация инспектора (предложенная группой GoF).

• Как устранить некоторые недостатки базовой реализации шаблона Visitor.

• Как реализовать решения, касающиеся реализации шаблона Visitor, в виде библиотеки.

• Мощные обобщенные компоненты, облегчающие реализацию инспекторов.

10.1. Основы шаблона Visitor

Рассмотрим иерархию классов, которую мы хотим наделить новыми функциональными возможностями. Для этого в нее можно добавлять либо новые классы, либо новые виртуальные функции-члены.

Добавить новые классы легко. Можно применить механизм наследования к терминальному классу и реализовать необходимые виртуальные функции. Для этого не нужно изменять или компилировать вновь существующие классы. Их коды остаются неизменными и готовы к повторному использованию.



в противоположность этому, добавить новую виртуальную функцию трудно. Для того чтобы полиморфно манипулировать объектами (с помощью указателей на объекты базового класса), виртуальную функцию-член нужно добавить не только в базовый класс, но и, возможно, во многие другие классы, входящие в иерархию. Это - основная операция. Она модифицирует базовый класс, от которого зависит вся иерархия и ее клиенты. В результате все придется повторно компилировать.

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

Однако допустим, что у нас есть иерархия, в которую редко добавляются новые классы и в то же время часто добавляются новые виртуальные функции-члены. В этой ситуации возможность легко добавлять новые классы представляет собой ненужное преимущество. Вот тут-то и пригодится щаблон visitor. Этот щаблон предоставляет профаммисту именно ту функциональную возможность, в которой он в данный момент нуждается, позволяя легко вставлять в иерархию новые виртуальные функции-члены, одновременно затрудняя вставку новых классов. За это придется заплатить всего-навсего лищним виртуальным вызовом.

Шаблон Visitor лучще всего проявляет свои возможности, когда операции на объектами не связаны между собой. Парадигма "состояние и операции" для каждого класса становится не совсем уместной. В данной ситуации лучще придерживаться принципа отделения типов от операций. Имеет смысл хранить разные реализации одной операции вместе, а не распределять их по иерархии классов.

Допустим, что мы разрабатываем текстовый редактор. Элементы документа, такие как абзащ.1 и графические изображения, представляются в виде классов, производнь[х от одного базового класса, скажем, DocElement. Документ - это сфуктурированный набор указателей на объекты класса DocElement. Профаммисту нужна возможность перемещаться по этой сфуктуре и выполнять операции, например, проверку орфофафии, переформатирование и вычисление статистических показателей. В идеале следовало бы реализовывать эти операции, добавив новый код без модификации существующей профаммы. Более того, профамму было бы легче сопровождать, если бы весь код, связанный, скажем, с вычислением статистических показателей, хранился в одном месте.

К статистическим показателям могут относиться общее количество символов, значащих символов, слов и рисунков. Все это естественным образом можно поместить в класс DocStats.

class DocStats {

unsigned int, chars ,

nonBlankChars ,

words ,

images ;

public:

void AddChars(unsigned int charsToAdd) {

chars += charsToAdd; }

... классы Addwords, Addlmages и др. определяются так же ... Статистика отображается в диалоговом окне void DisplayО; };



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

class DocElement {

эта функция-член предназначена для сбора статистики virtual void updateStatsCDocStats& statistics) = 0;

В этом случае каждый конкретный элемент документа может определять эту функцию по-своему. Например, классы Paragraph и RasterBitmap, производные от класса DocElement, могут реализовывать функцию updateStats следующим образом.

void Paragraph::updateStatsCDocStats& statistics) {

statistics.AddCharsC/соугы/ес/яво символов в абзаце) ; statistics.AddwordsC/соугы/ес/яво слов в абзаце);

void RasterBitmap::updateStatsCDocStats& statistics) {

Этот класс учитывает только рисунки и больше ничего (символы и другие элементы игнорируются) statistics.Addlmages(l);

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

voi d Document::Di splayStati sti cs() {

DocStats statistics;

for (каждый элемент документа)

3yzejMeHm->updateStats(statistics); }

statistics .DisplayO;

Это очень хорощая реализация возможностей по сбору статистики, однако у нее есть ряд недостатков.

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

• Фактические операции по сбору статистики рассеяны по разным реализациям функции UpdateStats. При отладке или расщирении возможностей, связанных со сбором статистики, придется искать и редактировать несколько файлов.

• Реализация не масштабируется по мере добавления новых операций, аналогичных сбору статистики. Для того чтобы добавить операцию "увеличить размер шрифта на один пункт", понадобится добавить в класс DocElement новую виртуальную функцию, преодолевая все связанные с этим трудности.

Однако связь, существующую между классами DocElement и DocStats, можно разорвать, если переместить все операции внутрь класса DocStats и позволить ему самому определять, какую операцию выполнять для того или иного типа. Для этого нужно, чтобы в



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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105