2005 г.
Обзор паттернов проектирования
Ольга Дубина
"Каждый паттерн описывает некую повторяющуюся проблему и ключ к ее разгадке, причем таким образом, что этим ключом можно пользоваться при решении самых разнообразных задач".
Christopher Alexander[1].
АННОТАЦИЯ
Данная работа представляет собой обзор нескольких наиболее значительных монографий, посвященных паттернам проектирования информационных систем. Материал оформлен в виде структурированного справочника, в который включены паттерны проектирования обьектов информационных систем, архитектурные системные паттерны и паттерны интеграции информационных систем. В справочнике приведены краткие описания паттернов проектирования, однако, несмотря на свою лаконичность, данные описания позволяют понять ключевые особенности каждого паттерна и успешно использовать его на практике.
Оглавление
1. ВВЕДЕНИЕ
Любой паттерн проектирования, используемый при разработке информационных систем, представляет собой формализованное описание часто встречающейся задачи проектирования, удачное решение данной задачи, а также рекомендации по применению этого решения в различных ситуациях. Кроме того, паттерн проектирования обязательно имеет общеупотребимое наименование. Правильно сформулированный паттерн проектирования позволяет, отыскав однажды удачное решение, пользоваться им снова и снова. Следует подчеркнуть, что важным начальным этапом при работе с паттернами является адекватное моделирование рассматриваемой предметной области. Это является необходимым как для получения должным образом формализованной постановки задачи, так и для выбора подходящих паттернов проектирования. В качестве примера монографии, в которой описаны основы построения модели анализа и модели проектирования, можно привести работу [ 2].
Сообразное использование паттернов проектирования дает разработчику ряд неоспоримых преимуществ. Приведем некоторые из них. Модель системы, построенная в терминах паттернов проектирования, фактически является структурированным выделением тех элементов и связей, которые значимы при решении поставленной задачи. Помимо этого, модель, построенная с использованием паттернов проектирования, более проста и наглядна в изучении, чем стандартная модель. Тем не менее, несмотря на простоту и наглядность, она позволяет глубоко и всесторонне проработать архитектуру разрабатываемой системы с использованием специального языка. Применение паттернов проектирования повышает устойчивость системы к изменению требований и упрощает неизбежную последующую доработку системы. Кроме того, трудно переоценить роль использования паттернов при интеграции информационных систем организации. Также следует упомянуть, что совокупность паттернов проектирования, по сути, представляет собой единый словарь проектирования, который, будучи унифицированным средством, незаменим для общения разработчиков друг другом.
Цель данной работы - создать единый краткий справочник, рассматривающий существующие паттернов проектирования на основе единых принципов. В настоящее время имеется обширная литература, включающая несколько фундаментальных монографий, уделяющих внимание той или иной тематике. Однако, по крайней мере в русскоязычной литературе, до настоящего времени отсутствовал такой справочник основных паттернов проектирования. Предлагаемый справочник паттернов проектирования будет полезен как начинающим разработчикам в качестве вводного пособия, так и опытным проектировщикам как каталог типовых решений задач, часто встречающихся при разработке информационных систем. Основой предлагаемого систематизированного обзора послужил каталог, созданный мной для повседневной работы в качестве постановщика.
Паттерны проектирования, собранные в данном справочнике, разделены на три большие группы:
- паттерны проектирования распределения обязанностей и взаимодействия отдельных классов или обьектов информационных систем;
- архитектурные паттерны;
- паттерны интегрирования информационных систем.
Хотя данное разделение, по - видимому, подразумевается профессионалами в области проектирования, мне нигде не встречалось явное систематическое обсуждение данной классификации. Существуют монографии, посвященные каждой отдельной группе паттернов, но нет их унифицированного рассмотрения на единых принципах.
Что касается вышеперечисленных групп паттернов, внутри каждой из них проведена дополнительная классификация. Проведено обобщение и в ряде случаев реструктурирование паттернов проектирования, описанных в различных монографиях, особенно это касается архитектурных паттернов, что делает данную классификацию до определенной степени оригинальной. Для простоты восприятия, мной предложено оформление описаний паттернов проектирования в виде таблиц, кроме того, имеется приложение со словарем терминов. При работе над словарем, многие разрозненные определения были подвергнуты корректировке, что позволило сделать систематическое изложение логически непротиворечивым.
В данную работу не включено описание элементов UML, использованных при построении диаграмм для иллюстрации паттернов проектирования. Всеобъемлющее описание может быть найдено в работе [ 3]. Сами UML диаграммы построены в Rational Rose.
2. ПРИНЦИП КЛАССИФИКАЦИИ ПАТТЕРНОВ ПРОЕКТИРОВАНИЯ
Сложные иерархированные структуры представляются как набор определенным образом типологизированных элементов и связей между ними. Кроме того, эффективной процедурой является многоуровневое представление структур. Переход с одного уровня представления на другой осуществляется путем выделения определенных подструктур, которые, в свою очередь рассматриваются в качестве "макроскопических" элементов, связанных между собой более простым и понятным образом. В свою очередь, элементы более низкого уровня могут быть названы "микроскопическими".
При проектировании в области информационных технологий в качестве вышеописанной структуры выступает система в том ее определении, которое дано в Приложении, см. раздел 7.1. В рассматриваемом подходе к проектированию система конфигурируется с использованием паттернов.
Низшим уровнем представления данной системы является описание ее в терминах классов (со своими атрибутами и операциями) и соответствующих им обьектов, выступающих в качестве "микроскопических" элементов, и отношений между ними, играющих роль связей, см. раздел 7.2. Примером "макроскопического " элемента следующего уровня является системная архитектура, представляющая собой базовую подструктуру рассматриваемой системы. Самым высоким уровнем является интеграция отдельных систем, которые в данном случае рассматриваются в качестве макроскопических элементов. Следует подчеркнуть, что на этом уровне связи фактически становятся метасвязями и строятся на основе методик, отличных от тех, которые используются на двух предыдущих уровнях. Базовым примером подобной метасвязи может служить интегрирующая среда, см. паттерн 5.1.2.
Соответственно, предлагаемая классификация паттернов проектирования отражает три вышеописанные уровня представления.
Следует упомянуть, что, поскольку паттернов проектирования полифункциональны, то выделение основных функций с целью отнесения отдельного паттерна к той или иной группе было проведено с некоторой долей субъективности. Дополнительные функции паттерна, как правило, приведены в описании данного паттерна.
3. ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ КЛАССОВ/ОБЬЕКТОВ
Согласно классификации, предложенной в предыдущем разделе, описание системы в терминах классов/обьектов следует считать низшим уровнем ее представления. В свою очередь, при моделировании системы на уровне классов/обьектов обычно проводят дополнительную типологизацию в двух аспектах, а именно, описывают структуру системы в терминах микроскопических элементов (см. раздел 2) и то, каким образом такая система обеспечивает требуемый функционал. Соответственно, среди паттернов проектирования выделены структурные паттерны, см. раздел 3.1 и паттерны распределения обязанностей между классами/объектами, 3.2. Поскольку отдельные объекты создаются и уничтожаются в процессе работы системы, выделена еще одна большая группа паттернов проектирования, которые служат для создания обьектов, 3.3.
Необходимо отметить наличие еще одной классификации паттернов, которое очевидно из наименования данного раздела: паттерны проектирования классов и паттерны проектирования обьектов (определения класса и объекта см. в разделе 7.2). В качестве примера паттернов проектирования классов можно привести "Фабричный метод", "Шаблонный метод"; паттернов проектирования обьектов - "Абстрактную фабрику", "Хранителя" и др.
Кроме этого необходимо отметить, что некоторые паттерны проектирования обьектов часто используются совместно, например, паттерн "Компоновщик" часть применяется вместе с "Итератором" или "Посетителем". Помимо этого, одну и ту же задачу можно решить используя различные паттерны проектирования классов/обьектов в качестве альтернативы, так, например, "Прототип" зачастую используют вместо "Абстрактной фабрики".
3.1 Структурные паттерны проектирования классов/обьектов
3.1.1 Адаптер (Adapter) - GoF
Проблема |
Необходимо обеспечить взаимодействие несовместимых интерфейсов или как создать единый устойчивый интерфейс для нескольких компонентов с разными интерфейсами. |
Решение |
Конвертировать исходный интерфейс компонента к другому виду с помощью промежуточного объекта - адаптера, то есть, добавить специальный объект с общим интерфейсом в рамках данного приложения и перенаправить связи от внешних обьектов к этому объекту - адаптеру. |
Пример |
Соответствует примеру из описания паттерна "Полиморфизм", см. п. 3.2.15. |
3.1.2 Декоратор (Decorator) или Оболочка (Wrapper) - GoF
Проблема |
Возложить дополнительные обязанности (прозрачные для клиентов) на отдельный объект, а не на класс в целом. |
Рекомендации |
Применение нескольких "Декораторов" к одному "Компоненту" позволяет произвольным образом сочетать обязанности, например, одно свойство можно добавить дважды. |
Решение |
Динамически добавить объекту новые обязанности не прибегая при этом к порождению подклассов (наследованию). "Компонент"определяет интерфейс для обьектов, на которые могут быть динамически возложены дополнительные обязанности, "КонкретныйКомпонент" определяет объект, на который возлагаются дополнительные обязанности, "Декоратор" - хранит ссылку на объект "Компонент" и определяет интерфейс, соответствующий интерфейсу "Компонента". "КонкретныйДекоратор" возлагает дополнительные обязанности на компонент. "Декоратор" переадресует запросы объекту "Компонент".
|
Преимущества |
Большая гибкость, чем у статического наследования: можно добавлять и удалять обязанности во время выполнения программы в то время как при использовании наследования надо было бы создавать новый класс для каждой дополнительной обязанности. Данный паттерн позволяет избежать перегруженных методами классов на верхних уровнях иерархии - новые обязанности можно добавлять по мере необходимости. |
Недостатки |
"Декоратор" и его "Компонент" не идентичны, и, кроме того, получается что система состоит из большого числа мелких обьектов, которые похожи друг на друга и различаются только способом взаимосвязи а не классом и не значениями своих внутренних переменных - такая система сложна в изучении и отладке. |
3.1.3 Заместитель (Proxy) или Суррогат (Surrogate) - GoF
Проблема |
Необходимо управлять доступом к объекту, так чтобы создавать громоздкие объекты "по требованию". |
Решение |
Создать суррогат громоздкого объекта. "Заместитель" хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту (объект класса "Заместитель" может обращаться к объекту класса "Субъект", если интерфейсы "РеальногоСубъекта" и "Субъекта" одинаковы). Поскольку интерфейс "РеальногоСубъекта" идентичен интерфейсу "Субъекта", так, что "Заместителя" можно подставить вместо "РеальногоСубъекта", контролирует доступ к "РеальномуСубъекту", может отвечать за создание или удаление "РеальногоСубъекта". "Субъект" определяет общий для "РеальногоСубъекта" и "Заместителя" интерфейс, так, что "Заместитель" может быть использован везде, где ожидается "РеальныйСубъект". При необходимости запросы могут быть переадресованы "Заместителем" "РеальномуСубъекту".
"Заместитель" может иметь и другие обязанности, а именно:
- удаленный "Заместитель" может отвечать за кодирование запроса и его аргументов и отправку закодированного запроса реальному "Субъекту",
- виртуальный "Заместитель" может кэшировать дополнительную информацию о реальном "Субъекте", чтобы отложить его создание,
- защищающий "Заместитель" может проверять, имеет ли вызывающий объект необходимые для выполнения запроса права.
|
3.1.4 Информационный эксперт (Information Expert)- GRASP
Проблема |
В системе должна аккумулироваться, рассчитываться и т. п. необходимая информация. |
Решение |
Назначить обязанность аккумуляции информации, расчета и т. п. некоему классу (информационному эксперту), обладающему необходимой информацией. |
Рекомендации |
Информационным экспертом может быть не один класс, а несколько. |
Пример |
Необходимо рассчитать общую сумму продажи. Имеются классы проектирования "Продажа", "ТоварПродажа" (продажа отдельного вида товара в рамках продажи в целом), "ТоварСпецификация" (описание конкретного вида товара).
Необходимо распределить обязанности по предоставлению информации и расчету между этими классами. Объект "Продажа" должен передать сообщение "Рассчитать промежуточную сумму" каждому экземпляру класса "ТоварПродажа" (которые, в свою очередь, передают сообщения "СообщитьЦену" объектам "ТоварСпецификация", с целью получения информации о цене экземпляра товара), и, затем, просуммировать полученные результаты. Промежуточную сумму рассчитывает объект "Товар Продажа". Таким образом, все три объекта являются информационными экспертами.
Диаграмма классов проектирования.
|
Преимущества |
Поддерживает инкапсуляцию, то есть объекты используют свои собственные данные для выполнения поставленных задач. |
Недостатки |
Если объект, обладающий наиболее полной информацией, например, о продаже (см. пример - класс "Продажа"), будет отвечать и за сохранение этой информации в базе данных, то получится, что логика приложения (моделирование продажи) и логика связи с базой данных "помещаются" в один класс (нарушение принципа разделения обязанностей основных объектов системы, и, кроме того, логика связи с базой данных будет дублироваться во многих других классах. |
3.1.5 Компоновщик (Composite) - GoF
Проблема |
Как обрабатывать группу или композицию структур обьектов одновременно? |
Решение |
Определить классы для композитных и атомарных обьектов таким образом, чтобы они реализовывали один и тот же интерфейс. |
Пример |
См. паттерн "Стратегия", 3.2.9, необходимо учесть несколько скидок различных видов (зависят от времени, типа покупателя, типом выбранного продукта. Как применять политику ценообразования? Вырабатывается стратегия приоритета скидок, объект "Продажа" не должен обладать информацией о применяемых скидках, но можно было бы применить стратегию расчета скидок. Создается новый класс "РасчетСкидкиАлгоритмКомпозит". |
3.1.6 Мост (Bridge), Handle (описатель) или Тело (Body) - GoF
Проблема |
Требуется отделить абстракцию от реализации так, чтобы и то и другое можно было изменять независимо. При использовании наследования реализация жестко привязывается к абстракции, что затрудняет независимую модификацию. |
Решение |
Поместить абстракцию и реализацию в отдельные иерархии классов. |
Рекомендации |
Можно использовать если, например, реализацию необходимо выполнять во время реализации программы. |
Пример |
"Абстракция" определяет интерфейс "Абстракции" и хранит ссылку на объект "Реализация", "УточненнаяАбстракция" расширяет интерфейс, определенный "Абстракцией". "Реализация" определяет интерфейс для классов реализации, он не обязан точно соответствовать интерфейсу класса "Абстракция" - оба интерфейса могут быть совершенно различны. Обычно интерфецйс класса "Реализация" предоставляет только примитивные операции, а класс "Абстракция" определяет операции более высокого уровня, базирующиеся на этих примитивных. "КонкретнаяРеализация" содержит конкретную реализацию класса "Реализация". Объект "Абстракция" перенаправляет своему объекту "Реализация" запросы "Клиента".
|
Преимущества |
Отделение реализации от интерфейса, то есть, "Реализацию" "Абстракции" можно конфигурировать во время выполнения. Кроме того, следует упомянуть, что разделение классов "Абстракция" и "Реализация" устраняет зависимости от реализации, устанавливаемые на этапе компиляции: чтобы изменить класс "Реализация" вовсе не обязательно перекомпилировать класс "Абстракция". |
3.1.7 Низкая связанность (Low Coupling) - GRASP
Проблема |
Обеспечить низкую связанность при создании экземпляра класса и связывании его с другим классом. |
Решение |
Распределить обязанности между объектами так, чтобы степень связанности оставалась низкой. |
Пример |
Необходимо создать экземпляр класса "Платеж". В предметной области регистрация объекта "Платеж" выполняется объектом "Регистрация" (ведется рестр). Ниже приводятся 2 способа создания экземпляра класса "Платеж". Верхний рисунок - с использованием паттерна "Создатель", нижний - с использованием "Низкая связанность". Последний способ обеспечивает более низкую степень связывания.
|
3.1.8 Приспособленец (Flyweight) - GoF
Проблема |
Необходимо обеспечить поддержку множества мелких обьектов. |
Рекомендации |
Приспособленцы моделируют сущности, число которых слишком велико для представления объектами. Имеет смысл использовать данный паттерн если одновременно выполняются следующие условия:
- в приложении используется большое число обьектов, из-за этого расходы на хранение высоки,
- большую часть состояния обьектов можно вынести вовне,
- многие группы обьектов можно заменить относительно небольшим количеством обьектов, поскольку состояния обьектов вынесены вовне.
|
Решение |
Создать разделяемый объект, который можно использовать одновременно в нескольких контекстах, причем, в каждом контексте он выглядит как независимый объект (неотличим от экземпляра, который не разделяется). "Приспособленец" объявляет интерфейс, с помощью которого приспособленцы могут получить внешнее состояние или как-то воздействовать на него, "КонкретныйПриспособленец" реализует интерфейс класса "Приспособленец" и добавляет при необходимости внутреннее состояние. Внутреннее состояние хранится в объекте "КонкретныйПриспособленец", в то время как внешнее состояние хранится или вычисляется "Клиентами" ("Клиент" передает его "Приспособленцу" при вызове операций).
Объект класса "КонкретныйПриспособленец" должен быть разделяемым. Любое сохраняемое им состояние должно быть внутренним, то есть независимым от контекста, "ПриспособленецФабрика" - создает объекты - "Приспособленцы" (или предоставляет существующий экземпляр) и управляет ими. "НеразделяемыйКонкретныйПриспособленец" - не все подклассы "Приспособленца" обязательно должны быть разделяемыми. "Клиент" - хранит ссылки на одного или нескольких "Приспособленцев", вычисляет и хранит внешнее состояние "Приспособленцев".
|
Преимущества |
Вследствие уменьшения общего числа экземпляров и вынесения состояния экономится память. |
3.1.9 Устойчивый к изменениям (Protected Variations) - GRASP
Проблема |
Как спроектировать систему так, чтобы изменение одних ее элементов не влияло на другие? |
Решение |
Идентифицировать точки возможных изменений или неустойчивости и распределить обязанности таким образом, чтобы обеспечить устойчивую работу системы. |
Пример |
Паттерн проектирования "Полиморфизм", см. 3.2.15 является хорошей иллюстрацией данного метода. В данном случае точкой вариации или неустойчивости являются интерфейсы внешних систем. При добавлении интерфейса "IНалоговаяСистемаАдаптер" на основе принципа полиморфизма получается, что внутренние объекты смогут взаимодействовать с устойчивым интерфейсом, а детали взаимодействия с внешними системами будут скрыты в конкретных реализациях адаптеров. |
3.1.10 Фасад (Facade) - GoF
Проблема |
Как обеспечить унифицированный интерфейс с набором разрозненных реализаций или интерфейсов, например, с подсистемой, если нежелательно высокое связывание с этой подсистемой или реализация подсистемы может измениться? |
Решение |
Определить одну точку взаимодействия с подсистемой - фасадный объект, обеспечивающий общий интерфейс с подсистемой и возложить на него обязанность по взаимодействию с ее компонентами. Фасад - это внешний объект, обеспечивающий единственную точку входа для служб подсистемы. Реализация других компонентов подсистемы закрыта и не видна внешним компонентам. Фасадный объект обеспечивает реализацию паттерна "Устойчивый к изменениям" с точки зрения защиты от изменений в реализации подсистемы., см. п. 3.1.9. |
|
|