2005 г.
Обзор паттернов проектирования
Ольга Дубина
3.2.8 Состояние (State) - GoF
Проблема |
Варьировать поведение объекта в зависимости от его внутреннего состояния |
Решение |
Класс "Контекст" делегирует зависящие от состояния запросы текущему объекту "КонкретноеСостояние" (хранит экземпляр подкласса "КонкретноеСостояние", которым определяется текущее состояние), и определяет интерфейс, представляющий интерес для клиентов. "КонкретноеСостояние" реализует поведение, ассоциированное с неким состоянием объекта "Контекст". "Состояние" определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным экземпляром "Контекста".
|
Преимущества |
Локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям, переходы между состояниями становятся явными. |
3.2.9 Стратегия (Strategy) - GoF
Проблема |
Спроектировать изменяемые, но надежные алгоритмы или стратегии. |
Решение |
Определить для каждого алгоритма или стратегии отдельный класс со стандартным интерфейсом. |
Пример |
Обеспечение сложной логики вычисления стоимости товаров с учетом сезонных скидок, скидок постоянным клиентам и т. п. Данная стратегия может изменяться.
Создается несколько классов "Стратегия", каждый из которых содержит один и тот же полиморфный метод "ЦенаРассчитать". В качестве параметров в этот метод передаются данные о продаже. Объект стратегии связывается с контекстным объектом (тем объектом, к которому применяется алгоритм). |
3.2.10 Хранитель (Memento) - GoF
Проблема |
Необходимо зафиксировать поведение объекта для реализации, например, механизма отката. |
Решение |
Зафиксировать и вынести (не нарушая инкапсуляции) за пределы объекта его внутреннее состояние так, чтобы впоследствии можно было восстановить в нем объект. "Хранитель" сохраняет внутреннее состояние объекта "Хозяин" и запрещает доступ к себе всем другим объектам кроме "Хозяина", который имеет доступ ко всем данным для восстановления в прежнем состоянии. "Посыльный" может лишь передавать "Хранителя" другим объектам. "Хозяин" создает "Хранителя", содержащего снимок текущего внутреннего состояния и использует "Хранитель" для восстановления внутреннего состояния. "Посыльный" отвечает за сохранение "Хранителя", при этом не производит никаких операций над "Хранителем" и не исследует его внутреннее содержимое. "Посыльный" запрашивает "Хранитель" у "Хозяина", некоторое время держит его у себя, а затем возвращает "Хозяину".
|
Преимущества |
Не раскрывается информация, которая доступна только "Хозяину", упрощается структура "Хозяина". |
Недостатки |
С использованием "Хранителей" могут быть связаны значительные издержки, если "Хозяин" должен копировать большой объём информации, или если копирование должно проводиться часто. |
3.2.11 Цепочка обязанностей (Chain of Responsibility) - GoF
Проблема |
Запрос должен быть обработан несколькими объектами. |
Рекомендации |
Логично использовать данный паттерн, если имеется более одного объекта, способного обработать запрос и обработчик заранее неизвестен (и должен быть найден автоматически) или если весь набор обьектов, которые способны обработать запрос, должен задаваться автоматически. |
Решение |
Связать объекты - получатели запроса в цепочку и передать запрос вдоль этой цепочки, пока он не будет обработан. "Обработчик" определяет интерфейс для обработки запросов, и, возможно, реализует связь с преемником, "КонкретныйОбработчик" обрабатывает запрос, за который отвечает, имеет доступ к своему преемнику ("КонкретныйОбработчик" направляет запрос к своему преемнику, если не может обработать запрос сам.
|
Преимущества |
Ослабляется связанность (объект не обязан "знать", кто именно обработает его запрос). |
Недостатки |
Нет гарантий, что запрос будет обработан, поскольку он не имеет явного получателя. |
3.2.12 Шаблонный метод (Template Method) - GoF
Проблема |
Определить алгоритм и реализовать возможность переопределения некоторых шагов алгоритма для подклассов (без изменения общей структуры алгоритма. |
Решение |
"АбстрактныйКласс" определяет абстрактные Операции(), замещаемые в конкретных подклассах для реализации шагов алгоритма, и реализует ШаблонныйМетод(), определяющий "скелет" алгоритма. "КонкретныйКласс" релизует Операции(), выполняющие шаги алгоритма способом, который зависит от подкласса. "КонкретныйКласс" предполагает, что инвариантные шаги алгоритма будут выполнены в "АбстрактномКлассе".
|
3.2.13 Высокое зацепление (High Cohesion) - GRASP
Проблема |
Необходимо обеспечить выполнение объектами разнородных функций. |
Решение |
Обеспечить распределение обязанностей с высоким зацеплением. |
Пример |
Если в примере для паттерна "Низкая связанность", см. 3.1.7 на класс "Регистрация" возлагать все новые и новые системные функции, связанные с системными операциями, то данный класс будет слишком перегружен и будет обладать низкой степенью зацепления. Второй рисунок для примера Low Coupling обладает более высоким уровнем зацепления и низким уровнем связывания (он является более предпочтительным). |
Преимущества |
Классы с высокой степенью зацепления просты в поддержке и повторном использовании. |
Недостатки |
Иногда бывает неоправданно использовать высокое зацепление для распределенных серверных обьектов. В этом случае для обеспечения быстродействия необходимо создать несколько более крупных серверных обьектов со слабым зацеплением. |
3.2.14 Контроллер (Controller) - GRASP
Проблема |
"Кто" должен отвечать за обработку входных системных событий? |
Решение |
Обязанности по обработке системных сообщений делегируются специальному классу. Контроллер - это объект, который отвечает за обработку системных событий и не относится к интерфейсу пользователя. Контроллер определяет методы для выполнения системных операций. |
Рекомендации |
Для различных прецедентов логично использовать разные контроллеры (контроллеры прецедентов) - контроллеры не должны быть перегружены.
Внешний контроллер представляет всю систему целиком, его можно использовать, если он будет не слишком перегруженным (то есть, если существует лишь несколько системных событий). |
Преимущества |
Удобно накапливать информацию о системных событиях (в случае, если системные операции выполняются в некоторой определенной последовательности).
Улучшаются условия для повторного использования компонентов (системные события обрабатываются Контроллером а не элементами интерфейса пользователя). |
Недостатки |
Контроллер может оказаться перегружен. |
3.2.15 Полиморфизм (Polymorphism) - GRASP
Проблема |
Как обрабатывать альтернативные варианты поведения на основе типа? Как заменять подключаемые компоненты системы? |
Решение |
Обязанности распределяются для различных вариантов поведения с помощью полиморфных операций для этого класса. Каждая внешняя система имеет свой интерфейс. |
Пример |
Интеграция разрабатываемой системы с различными внешними системами учета налогов. Используются локальные программные объекты, обеспечивающие адаптацию (Адаптеры), при отправке сообщения к такому объекту выполняется обращение к внешней системе с использованием ее собственного программного интерфейса.
Использование полиморфизма оправдано для адаптации к различным внешним системам. |
Преимущества |
Впоследствии легко расширять и модернизировать систему. |
Недостатки |
Не следует злоупотреблять добавлением интерфейсов с применением принципа полиморфизма с целью обеспечения дееспособности системы в неизвестных заранее новых ситуациях. |
3.2.16 Искусственный (Pure Fabrication) - GRASP
Проблема |
Какой класс должен обеспечивать реализацию паттернов "Высокое зацепление" 3.2.13, и "Низкая связанность" 3.1.7? |
Решение |
Присвоить группу обязанностей с высокой степенью зацепления классу, который не представляет конкретного понятия из предметной области (синтезировать искусственную сущность для обеспечения высокого зацепления и слабого связывания). |
Пример |
См. пример паттерна "Информационный эксперт" 3.1.4. Какой класс должен сохранять экземпляры класса "Продажа" в реляционной базе данных? Если возложить эту обязанность на класс "Продажа", то будем иметь низкую степень зацепления и высокую степень связывания (поскольку класс "Продажа" должен быть связан с интерфейсом реляционной базы данных. Хранение обьектов в реляционной базе данных - это общая задача, которую придется решать для многих классов. Решением данной проблемы будет создание нового класса "ПостоянноеХранилище", ответственного за сохранение обьектов некоторого вида в базе данных.
|
Преимущества |
Класс "ПостоянноеХранилище" будет обладать низкой степенью связывания и высокой степенью зацепления. |
Недостатки |
Данным паттерном не следует злоупотреблять иначе все функции системы превратятся в объекты. |
3.2.17 Перенаправление (Indirection) - GRASP
Проблема |
Как перераспределить обязанности обьектов, чтобы обеспечить отсутствие прямого связывания? |
Решение |
Присвоить обязанности по обеспечению связи между службами или компонентами промежуточному объекту. |
Пример |
См. пример к паттерну "Искусственный" 3.2.16. Класс "Хранилище" выступает в роли промежуточного звена между классом "Продажа" и базой данных. |
3.3 Порождающие паттерны проектирования
3.3.1 Абстрактная фабрика (Abstract Factory, Factory), др. название Инструментарий (Kit) - GoF
Проблема |
Создать семейство взаимосвязанных или взаимозависимых обьектов (не специфицируя их конкретных классов). |
Решение |
Создать абстрактный класс, в котором объявлен интерфейс для создания конкретных классов. |
Пример |
Какой класс должен отвечать за создание обьектов - адаптеров при использовании паттерна "Адаптер", см. 3.1.1. Если подобные объекты создаются неким объектом уровня предметной области, то будет нарушен принцип разделения обязанностей. |
Преимущества |
Изолирует конкретные классы. Поскольку "Абстрактная фабрика" инкапсулирует ответственность за создание классов и сам процесс их создания, то она изолирует клиента от деталей реализации классов.
Упрощена замена "Абстрактной фабрики", поскольку она используется в приложении только один раз при инстанцировании. |
Недостатки |
Интерфейс "Абстрактной фабрики" фиксирует набор обьектов, которые можно создать. Расширение "Абстрактной фабрики" для изготовления новых обьектов часто затруднительно. |
3.3.2 Одиночка (Singleton) - GoF
Проблема |
Какой специальный класс должен создавать "Абстрактную фабрику", см. 3.3.1 и как получить к ней доступ? Необходим лишь один экземпляр специального класса, различные объекты должны обращаться к этому экземпляру через единственную точку доступа. |
Решение |
Создать класс и определить статический метод класса, возвращающий этот единственный объект. |
Рекомендации |
Разумнее создавать именно статический экземпляр специального класса, а не объявить требуемые методы статическими, поскольку при использовании методов экземпляра можно применить механизм наследования и создавать подклассы. Статические методы в языках программирования не полиморфны и не допускают перекрытия в производных классах.
Решение на основе создания экземпляра является более гибким, поскольку впоследствии может потребоваться уже не единственный экземпляр объекта, а несколько. |
3.3.3 Прототип (Prototype) - GoF
Проблема |
Система не должна зависеть от того, как в ней создаются, компонуются и представляются объекты. |
Решение |
Создавать новые объекты с помощью паттерна - прототипа. "Прототип" объявляет интерфейс для клонирования самого себя. "Клиент" создает новый объект, обращаясь к "Прототипу" с запросом клонировать "Прототип".
|
3.3.4 Создатель экземпляров класса (Creator) - GRASP
Проблема |
"Кто" должен отвечать за создание экземпляров класса. |
Решение |
Назначить классу В обязанность создавать объекты другого класса А |
Рекомендации |
Логично использовать паттерн если класс В содержит, агрегирует, активно использует и т.п. объекты класса А. |
Пример |
См. пример к паттерну "Информационный эксперт" в п. 3.1.4, необходимо определить, какой объект должен отвечать за создание экземпляра "ТоварПродажа". Логично, чтобы это был объект "Продажа", поскольку он содержит (агрегирует) несколько обьектов "ТоварПродажа".
|
Преимущества |
Использование этого паттерна не повышает связанности, поскольку созданный класс, как правило, виден только для класса - создателя. |
Недостатки |
Если процедура создания объекта достаточно сложная (например выполняется на основе некоего внешнего условия), логично использовать паттерн "Абстрактная Фабрика", см. 3.3.1, то есть, делегировать обязанность создания обьектов специальному классу. |
3.3.5 Строитель (Builder) - GoF
Проблема |
Отделить конструирование сложного объекта от его представления, так чтобы в результате одного и того же конструирования могли получаться различные представления. Алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой. |
Решение |
"Клиент" создает объект - распорядитель "Директор" и конфигурирует его объектом - "Строителем". "Директор" уведомляет "Строителя" о том, что нужно построить очередную часть "Продукта". "Строитель" обрабатывает запросы "Директора" и добавляет новые части к "Продукту", затем "Клиент" забирает "Продукт" у "Строителя".
|
Преимущества |
Объект "Строитель" предоставляет объекту "Директор" абстрактный интерфейс для конструирования "Продукта", за которым может скрыть представление и внутреннюю структуру продукта, и , кроме того, процесс сборки "продукта". Для изменения внутреннего представления "Продукта" достаточно определить новый вид "Строителя". Данный паттерн изолирует код, реализующий создание объекта и его представление. |
3.3.6 (Фабричный метод) Factory Method или Виртуальный конструктор (Virtual Constructor) - GoF
Проблема |
Определить интерфейс для создания объекта, но оставить подклассам решение о том, какой класс инстанцировать, то есть, делегировать инстанцирование подклассам. |
Решение |
Абстрактный класс "Создатель" объявляет ФабричныйМетод, возвращающий объект типа "Продукт" (абстрактный класс, определяющий интерфейс обьектов, создаваемых фабричным методом). "Создатель также может определить реализацию по умолчанию ФабричногоМетода, который возвращает "КонкретныйПродукт". "КонкретныйСоздатель" замещает ФабричныйМетод, возвращающий объект "КонкретныйПродукт". "Создатель" "полагается" на свои подклассы в определении ФабричногоМетода, возвращающего объект "КонкретныйПродукт".
|
Преимущества |
Избавляет проектировщика от необходимости встраивать в код зависящие от приложения классы. |
Недостатки |
Возникает дополнительный уровень подклассов. |
|
|