Анимация
JavaScript
|
Главная Библионтека Шаблон 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 |