- •197110, Санкт-Петербург, Чкаловский пр., 15.
- •Глава 1. Введение в паттерны проектирования 15
- •Глава 2. Проектирование редактора документов 39
- •Глава 3. Порождающие паттерны 75
- •Глава 4. Структурные паттерны 109
- •Глава 5. Паттерны поведения 173
- •Глава 6. Заключение 271
- •Предисловие
- •Глава 1. Введение в паттерны проектирования
- •1.1. Что такое паттерн проектирования
- •1.2. Паттерны проектирования в схеме mvc в языке Smalltalk
- •1.3. Описание паттернов проектирования
- •1.4. Каталог паттернов проектирования
- •1.5. Организация каталога
- •1.6. Как решать задачи проектирования с помощью паттернов
- •Поиск подходящих объектов
- •Определение степени детализации объекта
- •Специфицирование интерфейсов объекта
- •Специфицирование реализации объектов
- •Механизмы повторного использования
- •Сравнение структур времени выполнения и времени компиляции
- •Проектирование с учетом будущих изменений
- •1.7. Как выбирать паттерн проектирования
- •1.8. Как пользоваться паттерном проектирования
- •Глава 2. Проектирование редактора документов
- •2.1. Задачи проектирования
- •2.2. Структура документа
- •Рекурсивная композиция
- •Паттерн компоновщик
- •2.3. Форматирование
- •Инкапсуляция алгоритма форматирования
- •Классы Compositor и Composition
- •Стратегия
- •2.4. Оформление пользовательского интерфейса
- •Прозрачное обрамление
- •Моноглиф
- •Паттерн декоратор
- •2.5. Поддержка нескольких стандартов внешнего облика
- •Абстрагирование создания объекта
- •Фабрики и изготовленные классы
- •Паттерн абстрактная фабрика
- •2.6. Поддержка нескольких оконных систем
- •Можно ли воспользоваться абстрактной фабрикой?
- •Инкапсуляция зависимостей от реализации
- •Классы Window и WindowImp
- •Подклассы WindowImp
- •Конфигурирование класса Window с помощью WindowImp
- •Паттерн мост
- •2.7. Операции пользователя
- •Инкапсуляция запроса
- •Класс Command и его подклассы
- •Отмена операций
- •История команд
- •Паттерн команда
- •2.8. Проверка правописания и расстановка переносов
- •Доступ к распределенной информации
- •Инкапсуляция доступа и порядка обхода
- •Класс Iterator и его подклассы
- •Паттерн итератор
- •Обход, и действия выполняемые при обходе
- •Класс Visitor и его подклассы
- •Паттерн посетитель
- •2.9. Резюме
- •Глава 3. Порождающие паттерны
- •Паттерн Abstract Factory
- •Паттерн Builder
- •Паттерн Factory Method
- •Паттерн Prototype
- •Паттерн Singleton
- •Обсуждение порождающих паттернов
- •Глава 4. Структурные паттерны
- •Паттерн Adapter
- •Паттерн Bridge
- •Паттерн Composite
- •Паттерн Decorator
- •Паттерн Facade
- •Паттерн Flyweight
- •Паттерн Proxy
- •Обсуждение структурных паттернов
- •Адаптер и мост
- •Компоновщик, декоратор и заместитель
- •Глава 5. Паттерны поведения
- •Паттерн Chain of Responsibility
- •Паттерн Command
- •Паттерн Interpreter
- •Паттерн Iterator
- •Паттерн Mediator
- •Паттерн Memento
- •Паттерн Observer
- •Паттерн State
- •Паттерн Strategy
- •Паттерн Template Method
- •Паттерн Visitor
- •Обсуждение паттернов поведения Инкапсуляция вариаций
- •Объекты как аргументы
- •Должен ли обмен информацией быть инкапсулированным или распределенным
- •Разделение получателей и отправителей
- •Глава 6. Заключение
- •6.1. Чего ожидать от паттернов проектирования
- •Единый словарь проектирования
- •Помощь при документировании и изучении
- •Дополнение существующих методов
- •Цель реорганизации
- •6.2. Краткая история
- •6.3. Проектировщики паттернов
- •Языки паттернов Александра
- •Паттерны в программном обеспечении
- •6.4. Приглашение
- •6.5. На прощание
- •Приложение а. Глоссарий
- •Приложение в. Объяснение нотации
- •В.1. Диаграмма классов
- •В.2. Диаграмма объектов
- •В.3. Диаграмма взаимодействий
- •Приложение с. Базовые классы
- •Библиография
- •Алфавитный указатель
Класс Visitor и его подклассы
Мы будем использовать термин «посетитель» для обозначения класса объектов, «посещающих» другие объекты во время обхода, дабы сделать то, что необходимо в данном контексте12. Тогда мы можем определить класс Visitor, описывающий абстрактный интерфейс для посещения глифов в структуре:
class Visitor {
public:
virtual void VisitCharacter(Character*) { }
virtual void VisitRow(Row*) { }
virtual void VisitImage(Image*) { }
// ... и так далее
};
Конкретные подклассы Visitor выполняют разные виды анализа. Например, можно было определить подкласс SpellingCheckingVisitor для проверки правописания и подкласс HyphenationVisitor для расстановки переносов. При этом SpellingCheckingVisitor был бы реализован точно так же, как мы реализовали класс SpellingChecker выше, только имена операций отражали бы более общий интерфейс класса Visitor. Так, операция CheckCharacter называлась бы VisitCharacter.
Поскольку имя CheckMe не подходит для посетителей, которые ничего не проверяют, мы использовали бы имя Accept. Аргумент этой операции тоже пришлось бы изменить на Visitor&, чтобы отразить тот факт, что может приниматься любой посетитель. Теперь для добавления нового вида анализа нужно лишь определить новый подкласс класса Visitor, а трогать классы глифов вовсе не обязательно. Мы поддержали все возможные в будущем виды анализа, добавив лишь одну операцию в класс Glyph и его подклассы.
О выполнении проверки правописания говорилось выше. Такой же подход будет применен для аккумулирования текста в подклассе HyphenationVisitor. Но после того как операция VisitCharacter из подкласса HyphenationVisitor закончила распознавание целого слова, она ведет себя по-другому. Вместо проверки орфографии применяется алгоритм расстановки переносов, чтобы определить, в каких местах можно перенести слово на другую строку (если это вообще возможно). Затем для каждой из найденных точек в структуру вставляется разделяющий (discretionary) глиф. Разделяющие глифы являются экземплярами подкласса Glyph – класса Discretionary.
Разделяющий глиф может выглядеть по-разному в зависимости от того, является он последним символом в строке или нет. Если это последний символ, глиф выглядит как дефис, в противном случае не отображается вообще. Разделяющий глиф запрашивает у своего родителя (объекта Row), является ли он последним потомком, и делает это всякий раз, когда от него требуют отобразить себя или вычислить свои размеры. Стратегия форматирования трактует разделяющие глифы точно так же, как пропуски, считая их «кандидатами» на завершающий символ строки. На диаграмме ниже показано, как может выглядеть встроенный разделитель.
Паттерн посетитель
Вышеописанная процедура – пример применения паттерна посетитель. Его главными участниками являются класс Visitor и его подклассы. Паттерн посетитель абстрагирует метод, позволяющий иметь заранее неопределенное число видов анализа структур глифов без изменения самих классов глифов. Еще одна полезная особенность посетителей состоит в том, что их можно применять не только к таким агрегатам, как наши структуры глифов, но и к любым структурам, состоящим из объектов. Сюда входят множества, списки и даже направленные ациклические графы. Более того, классы, которые обходит посетитель, необязательно должны быть связаны друг с другом через общий родительский класс. А это значит, что посетители могут пересекать границы иерархий классов.
Важный вопрос, который надо задать себе перед применением паттерна посетитель, звучит так: «Какие иерархии классов наиболее часто будут изменяться?» Этот паттерн особенно удобен, если необходимо выполнять действия над объектами, принадлежащими классу со стабильной структурой. Добавление нового вида посетителя не требует изменять структуру класса, что особенно важно, когда класс большой. Но при каждом добавлении нового подкласса вы будете вынуждены обновить все интерфейсы посетителя с целью включить операцию Visit... для этого подкласса. В нашем примере это означает, что добавление подкласса Foo класса Glyph потребует изменить класс Visitor и все его подклассы, чтобы добавить операцию VisitFoo. Однако при наших проектных условиях гораздо более вероятно добавление к Lexi нового вида анализа, а не нового вида глифов. Поэтому для наших целей паттерн посетитель вполне подходит.