
- •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. Диаграмма взаимодействий
- •Приложение с. Базовые классы
- •Библиография
- •Алфавитный указатель
Класс Iterator и его подклассы
Мы применим абстрактный класс Iterator для определения общего интерфейса доступа и обхода. Конкретные подклассы вроде ArrayIterator и ListIterator реализуют данный интерфейс для предоставления доступа к массивам и спискам, а такие подклассы, как PreorderIterator, PostorderIterator и им подобные, реализуют разные виды обходов структур. Каждый подкласс класса Iterator содержит ссылку на структуру, которую он обходит. Экземпляры подкласса инициализируются этой ссылкой при создании. На рис. 2.13 показан класс Iterator и несколько его подклассов. Обратите внимание, что мы добавили в интерфейс класса Glyph абстрактную операцию CreateIterator для поддержки итераторов.
Рис. 2.13. Класс Iterator и его подклассы
Интерфейс итератора предоставляет операции First, Next и IsDone для управления обходом. В классе ListIterator реализация операции First указывает на первый элемент списка, a Next перемещает итератор на следующий элемент. Операция IsDone возвращает признак, говорящий о том, перешел ли указатель за последний элемент списка. Операция CurrentItem разыменовывает итератор для возврата глифа, на который он указывает. Класс ArrayIterator делает то же самое с массивами глифов.
Теперь мы можем получить доступ к потомкам в структуре глифа, не зная ее представления:
Glyph* g;
Iterator<Glyph*>* i = g->CreateIterator();
for (i->First(); !i->IsDone(); i->Next()) {
Glyph* child = i->CurrentItem();
// выполнить действие с потомком
}
CreateIterator по умолчанию возвращает экземпляр NullIterator. NullIterator – это вырожденный итератор для глифов, у которых нет потомков, то есть листовых глифов. Операция IsDone для NullIterator всегда возвращает истину.
Подкласс глифа, имеющего потомков, замещает операцию CreateIterator так, что она возвращает экземпляр другого подкласса класса Iterator. Какого именно – зависит от структуры, в которой содержатся потомки. Если подкласс Row класса Glyph размещает потомков в списке, то его операция CreateIterator будет выглядеть примерно так:
Iterator<Glyph*>* Row::CreateIterator() {
return new ListIterator<Glyph*>(_children);
}
Для обхода в прямом и внутреннем порядке итераторы реализуют алгоритм обхода в терминах, определенных для конкретных глифов. В обоих случаях итератору передается корневой глиф той структуры, которую нужно обойти. Итераторы вызывают CreateIterator для каждого глифа в этой структуре и сохраняют возвращенные итераторы в стеке.
Например, класс PreorderIterator получает итератор от корневого глифа, инициализирует его так, чтобы он указывал на свой первый элемент, а затем помещает в стек:
void PreorderIterator::First () {
Iterator<Glyph*>* i = _root->CreateIterator();
if (i) {
i->First();
_iterators.RemoveAll();
_iterators.Push(i);
}
}
CurrentItem должна будет просто вызвать операцию CurrentItem для итератора на вершине стека:
Glyph* PreorderIterator::CurrentItem () const {
return
_iterators.Size() > 0 ?
_iterators.Top()->CurrentItem() : 0;
}
Операция Next обращается к итератору с вершины стека с тем, чтобы элемент, на который он указывает, создал свой итератор, спускаясь тем самым по структуре глифов как можно ниже (это ведь прямой порядок, не так ли?). Next устанавливает новый итератор так, чтобы он указывал на первый элемент в порядке обхода, и помещает его в стек. Затем Next проверяет последний встретившийся итератор; если его операция IsDone возвращает true, то обход текущего поддерева (или листа) закончен. В таком случае Next снимает итератор с вершины стека и повторяет всю последовательность действий, пока не найдет следующее не полностью обойденное дерево, если таковое существует. Если же необойденных деревьев больше нет, то мы закончили обход:
void PreorderIterator::Next () {
Iterator<Glyph*>* i =
_iterators.Top()->CurrentItem()->CreateIterator();
i->First();
_iterators.Push(i);
while (
_iterators.Size() > 0 && _iterators.Top()->IsDone()
) {
delete _iterators.Pop();
_iterators.Top()->Next();
}
}
Обратите внимание, что класс Iterator позволяет вводить новые виды обходов, не изменяя классы глифов, – мы просто порождаем новый подкласс и добавляем новый обход так, как проделали это для PreorderIterator. Подклассы класса Glyph пользуются тем же самым интерфейсом, чтобы предоставить клиентам доступ к своим потомкам, не раскрывая внутренней структуры данных, в которой они хранятся. Поскольку итераторы сохраняют собственную копию состояния обхода, то одновременно можно иметь несколько активных итераторов для одной и той же структуры. И, хотя в нашем примере мы занимались обходом структур глифов, ничто не мешает параметризовать класс типа PreorderIterator типом объекта структуры. В C++ мы воспользовались бы для этого шаблонами. Тогда описанный механизм итераторов можно было бы применить для обхода других структур.