
- •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. Диаграмма взаимодействий
- •Приложение с. Базовые классы
- •Библиография
- •Алфавитный указатель
Инкапсуляция доступа и порядка обхода
Пока что в нашем интерфейсе глифов для доступа к потомкам со стороны клиентов используется целочисленный индекс. Хотя для тех классов глифов, которые содержат потомков в массиве, это, может быть, и разумно, но совершенно неэффективно для глифов, пользующихся связанным списком. Роль абстракции глифов в том, чтобы скрыть структуру данных, в которой содержатся потомки. Тогда мы сможем изменить данную структуру, не затрагивая другие классы.
Поэтому только глифу разрешено знать, какую структуру он использует. Отсюда следует, что интерфейс глифов не должен отдавать предпочтение какой-то одной структуре данных. Например, не следует оптимизировать его в пользу массивов, а не связанных списков, как это делалось до сих пор.
Мы можем решить проблему и одновременно поддержать несколько разных способов обхода. Разумно включить возможности множественного доступа и обхода прямо в классы глифов и предоставить способ выбирать между ними, возможно, передавая константу, являющуюся элементом некоторого перечисления. Выполняя обход, классы передают этот параметр друг другу, чтобы гарантировать, что все они обходят структуру в одном и том же порядке. Так же должна передаваться любая информация, собранная во время обхода.
Для поддержки данного подхода мы могли бы добавить в интерфейс класса Glyph следующие абстрактные операции:
void First(Traversal kind)
void Next()
bool IsDone()
Glyph* GetCurrent()
void Insert(Glyph*)
Операции First, Next и IsDone управляют обходом. First производит инициализацию. В качестве параметра передается вид обхода в виде перечисляемой константы тина Traversal, которая может принимать такие значения, как CHILDREN (обходить только прямых потомков глифа), PREORDER (обходить всю структуру в прямом порядке), POSTORDER (в обратном порядке) или INORDER (во внутреннем порядке). Next переходит к следующему глифу в порядке обхода, a IsDone сообщает, закончился ли обход. GetCurrent заменяет операцию Child – осуществляет доступ к текущему в данном обходе глифу. Старая операция Insert переписывается, теперь она вставляет глиф в текущую позицию.
При анализе можно было бы использовать следующий код на C++ для обхода структуры глифов с корнем в g в прямом порядке:
Glyph* g;
for (g->First(PREORDER); !g->IsDone(); g->Next()) {
Glyph* current = g->GetCurrent();
// выполнить анализ
}
Обратите внимание, что мы исключили целочисленный индекс из интерфейса глифов. Не осталось ничего, что предполагало бы какой-то предпочтительный контейнер. Мы также уберегли клиенты от необходимости самостоятельно реализовывать типичные виды доступа.
Но этот подход еще не идеален. Во-первых, здесь не поддерживаются новые виды обхода, не расширяется множество значений перечисления и не добавляются новые операции. Предположим, что нам нужен вариант прямого обхода, при котором автоматически пропускаются нетекстовые глифы. Тогда пришлось бы изменить перечисление Traversal, включив в него значение TEXTUAL_PREORDER.
Но нежелательно менять уже имеющиеся объявления. Помещение всего механизма обхода в иерархию класса Glyph затрудняет модификацию и расширение без изменения многих других классов. Механизм также трудно использовать повторно для обхода других видов структур. И еще нельзя иметь более одного активного обхода над данной структурой.
Как уже не раз бывало, наилучшее решение – инкапсулировать изменяющуюся сущность в класс. В данном случае это механизмы доступа и обхода. Допустимо ввести класс объектов, называемых итераторами, единственное назначение которых – определить разные наборы таких механизмов. Можно также воспользоваться наследованием для унификации доступа к разным структурам данных и поддержки новых видов обхода. Тогда не придется изменять интерфейсы глифов или трогать реализации существующих глифов.