Шаблон проектирования

Шаблон проектирования (англ. design pattern или англ. software design pattern) — это обобщённое, многократно используемое решение часто встречающейся проблемы в различных контекстах проектирования программных систем[1]. Шаблон проектирования не является жёсткой структурой, которую нужно механически вставить в исходный код, а представляет собой описание или заготовку решения определённого класса проблем, которую можно применить в различных ситуациях[2]. Шаблоны проектирования можно рассматривать как формализованные лучшие практики, которые программист может использовать для решения типичных задач при проектировании программных приложений или систем.

Шаблоны проектирования, ориентированные на объектно-ориентированное программирование, обычно показывают отношения и взаимодействие между классами или объектами, не уточняя конкретные классы или объекты приложения. Шаблоны, предполагающие изменяемое состояние, могут быть неудачным выбором для функциональных языков программирования. Некоторые шаблоны становятся ненужными при наличии в языке готовой поддержки соответствующих механизмов, а объектно-ориентированные шаблоны не всегда подходят для не-ООП-языков.

Шаблоны проектирования могут рассматриваться как структурированный подход к программированию, занимающий промежуточное положение между уровнем парадигмы программирования и конкретного алгоритма.

История

Понятие шаблона изначально возникло как архитектурная концепция у Кристофера Александера ещё в 1977 году, в книге англ. A Pattern Language (ср. его статья «The Pattern of Streets», JOURNAL OF THE AIP, сентябрь 1966, том 32, №5, с. 273–278). В 1987 году Кент Бек и Уорд Каннингем начали эксперименты по применению шаблонов к программированию — в частности, языки шаблонов — и представили результаты на конференции OOPSLA в том же году[3].[4] В последующие годы Бек, Каннингем и другие продолжили развитие темы.

Шаблоны проектирования получили широкое распространение в информатике после публикации в 1994 году книги Design Patterns: Elements of Reusable Object-Oriented Software, написанной так называемой «Бандой четырёх» (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес, англ. Gang of Four, часто сокращается как GoF). В том же году прошла первая конференция Pattern Languages of Programming, а на следующий год был создан Portland Pattern Repository для документирования шаблонов проектирования. Сам термин и рамки его использования продолжают обсуждаться. К знаковым книгам о шаблонах проектирования относятся:

  • Gamma, Erich. Design Patterns: Elements of Reusable Object-Oriented Software / Erich Gamma, Richard Helm, Ralph Johnson … [и др.]. — Addison-Wesley, 1994. — ISBN 978-0-201-63361-0.
  • Brinch Hansen, Per. Studies in Computational Science: Parallel Programming Paradigms. — Prentice Hall, 1995. — ISBN 978-0-13-439324-7.
  • Buschmann, Frank. Pattern-Oriented Software Architecture, Volume 1: A System of Patterns / Frank Buschmann, Regine Meunier, Hans Rohnert … [и др.]. — John Wiley & Sons, 1996. — ISBN 978-0-471-95869-7.
  • Beck, Kent. Smalltalk Best Practice Patterns. — Prentice Hall, 1997. — ISBN 978-0134769042.
  • Schmidt, Douglas C. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects / Douglas C. Schmidt, Michael Stal, Hans Rohnert … [и др.]. — John Wiley & Sons, 2000. — ISBN 978-0-471-60695-6.
  • Fowler, Martin. Patterns of Enterprise Application Architecture. — Addison-Wesley, 2002. — ISBN 978-0-321-12742-6.
  • Hohpe, Gregor. Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions / Gregor Hohpe, Bobby Woolf. — Addison-Wesley, 2003. — ISBN 978-0-321-20068-6.
  • Freeman, Eric T. Head First Design Patterns / Eric T. Freeman, Elisabeth Robson, Bert Bates … [и др.]. — O'Reilly Media, 2004. — ISBN 978-0-596-00712-6.
  • Larman, Craig. Applying UML and Patterns (3rd Ed, 1st Ed 1995). — Pearson, 2004. — ISBN 978-0131489066.

Хотя шаблоны использовались на практике давно, формализация концепции шаблонов проектирования заняла довольно много лет[5].

Практика применения

Использование шаблонов проектирования может ускорить процесс разработки за счёт применения проверенных методик[6]. Эффективное проектирование программ требует учёта таких аспектов, которые могут стать явными только на поздних стадиях реализации. Новонаписанный код часто содержит скрытые проблемы, которые проявляются лишь со временем и могут приводить к серьёзным ошибкам. Повторное использование шаблонов проектирования помогает предупредить такие ситуации[7] и сделать код более понятным для тех, кто знаком с этими шаблонами.

Методы проектирования программного обеспечения сложно обобщить на более широкий класс задач. Шаблоны проектирования предоставляют общие решения, задокументированные в форме, не привязанной к конкретным деталям задачи.

В 1996 году Кристофер Александер выступил с программной речью на конференции OOPSLA, где рассказал о развитии шаблонов в архитектуре и выразил надежду, что сообщество программных шаблонов сможет помочь архитектуре создать «живые структуры» на основе генеративных схем, близких по духу к программному коду.

Мотивы и микроархитектура

Шаблон проектирования описывает мотив проектирования — то есть прототипную микроархитектуру, набор компонентов программы (например, классов, методов и др.) и их взаимосвязей. Разработчик адаптирует этот мотив под свою задачу для решения заявленной проблемы. Таким образом, итоговый код приобретает структуру и организацию, аналогичную выбранному мотиву.

Отраслевые и предметные шаблоны

Проводится работа по систематизации шаблонов проектирования в конкретных предметных областях, как с использованием существующих шаблонов, так и созданием специализированных шаблонов. Примеры включают шаблоны проектирования пользовательских интерфейсов[8], визуализации данных[9], защищённого проектирования[10], «безопасной юзабилити»[11], шаблоны для веб-дизайна[12] и проектирования бизнес-моделей[13].

Сборники докладов ежегодной конференции Pattern Languages of Programming содержат обширные примеры таких отраслевых шаблонов.

Объектно-ориентированное программирование

Шаблоны проектирования для объектно-ориентированных языков программирования показывают типовые способы взаимодействия и отношения между классами и объектами. При этом не всегда определяются конечные классы или объекты, участвующие в реализации. Шаблоны, основанные на изменяемом состоянии, плохо применимы в функциональных языках программирования. Некоторые шаблоны могут оказаться ненужными в языках, где их задача решается непосредственно средствами языка, а ООП-шаблоны не всегда целесообразны для не объектно-ориентированных языков.

Примеры

Шаблоны проектирования традиционно подразделяются в зависимости от решаемных ими задач. Порождающие шаблоны создают объекты; структурные шаблоны организуют классы и объекты, создавая более сложные структуры с новой функциональностью; поведенческие шаблоны описывают сотрудничество между объектами.

Порождающие шаблоны

Название Описание В Design Patterns В Code Complete[14] Другое
Абстрактная фабрика Интерфейс для создания семейств связанных объектов без указания их конкретных классов. Да Да н/д
Строитель Разделение построения сложного объекта от его представления так, чтобы один и тот же процесс построения мог создавать разные представления. Да Да н/д
Внедрение зависимостей Класс получает необходимые ему объекты через внешний инжектор, а не создаёт их напрямую. н/д Да н/д
Фабричный метод Определяет интерфейс для создания объекта, позволяя подклассам решать, какой класс инстанцировать. Фабричный метод делегирует создание объекта подклассам. Да Да н/д
Ленивая инициализация Отложенное создание объекта или выполнение затратной операции до первого обращения. В каталоге GoF реализовано как стратегия «виртуального прокси» в шаблоне Прокси. Да Да PoEAA[15]
Мультитон Гарантирует, что у класса есть только именованные экземпляры, и предоставляет глобальную точку доступа к ним. Да Да Да
Объектный пул Позволяет повторно использовать (рециклирует) объекты, больше не используемые, чтобы избежать затрат на их создание и уничтожение. Обобщение шаблонов подключения и пула потоков. Да Да Да
Прототип Определяет виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа, что повышает производительность и уменьшает расход памяти. Да Да Да
Получение ресурса во владение Гарантирует корректное освобождение ресурсов, связывая их жизненный цикл с объектом. Да Да Да
Одиночка (Singleton) Обеспечивает существование только одного экземпляра класса и предоставляет к нему глобальную точку доступа. Да Да Да

Структурные шаблоны

Название Описание В Design Patterns В Code Complete[14] Другое
Адаптер, Обёртка, Транслятор Преобразует интерфейс класса к ожидаемому интерфейсу клиента. Позволяет классам работать вместе, даже если их интерфейсы несовместимы. Эквивалент Enterprise Integration Pattern Translator. Да Да Да
Мост Отделяет абстракцию от реализации, позволяя им изменяться независимо. Да Да Да
Компоновщик Объединяет объекты в древовидные структуры для представления иерархии «часть-целое». Позволяет клиентам единообразно работать как с отдельными объектами, так и с их композициями. Да Да Да
Декоратор Динамически добавляет объекту новые обязанности, сохраняя прежний интерфейс. Более гибкая альтернатива наследованию. Да Да Да
Делегирование Расширяет класс через композицию вместо наследования. Объект обрабатывает запрос, делегируя действие другому объекту. Да Да Да
Объект расширения Добавляет функциональность иерархии без изменения самой иерархии. Да Да Да
Фасад Предоставляет унифицированный интерфейс к группе интерфейсов подсистемы, упрощая её использование для клиентов. Да Да Да
Приспособленец Эффективно поддерживает большое число похожих объектов с помощью совместного использования состояния. Да Да Да
Фронт-контроллер Централизованная точка входа для обработки запросов (часто в веб-приложениях). Да Да

J2EE Patterns[16] PoEAA[17]

Маркерный интерфейс Пустой интерфейс для маркировки класса метаданными. Да Да Effective Java[18]
Модуль Группирует связанные элементы (классы, одиночки, методы), используемые глобально, в единую концептуальную сущность. Да Да Да
Прокси Предоставляет суррогат или заместитель другого объекта для контроля доступа. Да Да Да
Твин[19] Позволяет моделировать множественное наследование в языках, не поддерживающих его напрямую. Да Да Да

Поведенческие шаблоны

Название Описание В Design Patterns В Code Complete[14] Другое
Черная доска Шаблон для объединения разнотипных источников данных (см. систему blackboard в ИИ). Да Да Да
Цепочка обязанностей Позволяет более чем одному объекту обработать запрос, передавая его по цепочке, пока один из объектов не обработает его. Да Да Да
Команда Инкапсулирует запрос как объект, позволяя параметризовать клиентов разными запросами, ставить в очередь и записывать запросы, а также реализовать отмену операций. Да Да Да
Fluent интерфейс АПИ, сочетающее вызовы методов в цепочку, чтобы они выглядели как DSL (предметно-ориентированный язык). Да Да Да
Интерпретатор Определяет представление для грамматики языка и интерпретатор для выполнения выражений этого языка. Да Да Да
Итератор Предоставляет способ последовательного доступа ко всем элементам объекта-агрегата без раскрытия его внутреннего устройства. Да Да Да
Посредник Определяет объект, инкапсулирующий взаимодействие множества других объектов, снижая их связанность. Да Да Да
Снимок (Memento) Не нарушая инкапсуляции, фиксирует и внешне хранит внутреннее состояние объекта для возможного восстановления. Да Да Да
Null object Избегает null-значений, предоставляя некий типовой объект по умолчанию. Да Да Да
Наблюдатель, публикация-подписка Определяет зависимость «один-ко-многим», при которой изменение состояния одного объекта приводит к оповещению и обновлению всех других, зависящих от него. Да Да Да
Слуга (Servant) Реализует общую функциональность для группы классов (часто в виде вспомогательных или утилитных классов с методами для разных объектов). Да Да Да
Спецификация Логика предметной области, комбинируемая булевым образом. Да Да Да
Состояние Позволяет объекту менять своё поведение при изменении внутреннего состояния, таким образом как будто он изменяет свой класс. Да Да Да
Стратегия Инкапсулирует семейство алгоритмов и делает их взаимозаменяемыми. Да Да Да
Шаблонный метод Определяет «скелет» алгоритма, делегируя отдельные шаги подклассам. Подклассы могут переопределять части алгоритма. Да Да Да
Посетитель (Visitor) Реализует операцию для множества разнотипных классов, не изменяя их. Да Да Да

Шаблоны конкурентности

Название Описание В POSA2[20] Другое
Active Object Отделяет выполнение метода от его вызова (каждый объект в своём потоке управления). Да н/д
Блокировка (Balking) Выполняет действие только если объект находится в определённом состоянии. Нет н/д
Синхронизация свойств Объединяет наблюдателей для синхронизации свойств нескольких объектов[21]. Нет н/д
Компьют-ядерное ядро Параллельное выполнение одних и тех же расчетов (например, на GPU при умножении матриц или в сверточных сетях). Нет н/д
Двойная проверка блокировки Минимизирует накладные расходы на захват блокировки проверкой условия вне и внутри синхронизации. Может быть небезопасен на определённых платформах — иногда рассматривается как антишаблон. Да н/д
Асинхронная обработка событий Решает проблемы асинхронности в многопоточных приложениях[22]. Нет н/д
Guarded suspension Управляет операциями, требующими наличия лока и соблюдения предусловия до выполнения. Нет н/д
Join Высокоуровневое решение для параллелизма и распределённых программ с помощью передачи сообщений. Нет н/д
Блокировка (Lock) Один поток захватывает ресурс, препятствуя доступу к нему других потоков[23]. Нет PoEAA[15]
Шаблон сообщений Организация обмена информацией (сообщениями) между компонентами и приложениями. Нет н/д
Монитор (объект) Объект, методы которого защищены взаимным исключением. Да н/д
Реактор Асинхронный интерфейс к ресурсам, требующим синхронной обработки. Да н/д
Блокировка на чтение-запись Позволяет конкурентно читать, но требует эксклюзива на запись; может использовать семафоры или технологию copy-on-write. Нет н/д
Диспетчер Явное управление очередностью выполнения потоков. Нет н/д
Обработчик сервиса Для каждого запроса сервер выделяет отдельный поток-обработчик[24]. Также известно как thread-per-session[25]. Нет н/д
Пул потоков Несколько потоков выполняют задачи из очереди. Обычно задач больше, чем потоков. Приватный случай шаблона Объектный пул. Нет н/д
Хранилище потока Локальная память потока, недоступная другим потокам. Да н/д
Безопасная конкурентность с эксклюзивным владением Исключает потребность в конкурентных механизмах, если можно доказать эксклюзивность владения переменной (например, в языке Rust). Нет н/д
Атомарная операция процессора Современные процессоры поддерживают атомарные инструкции, обеспечивающие безопасную работу с примитивами. Эти методы позволяют реализовать вышеописанные шаблоны конкурентности. Нет н/д

Документирование шаблонов

Документация шаблона проектирования описывает контекст применения, силы (взаимодействия или требования), которые шаблон стремится уравновесить, и предлагаемое решение[26]. Не существует единого общепринятого шаблона оформления документации — используются различные форматы. Однако, по мнению Мартина Фаулера, некоторые структуры описания приобрели известность и стали базой для создания новых шаблонов[27]. Одним из распространённых форматов является структура, предложенная Эрихом Гамма, Ричардом Хелмом, Ральфом Джонсоном и Джоном Влиссидесом в книге Design Patterns:

  • Название и классификация шаблона: уникальное и описательное название, помогающее ссылаться на шаблон.
  • Назначение (Intent): цель шаблона и мотивация его использования.
  • Также известен как: другие имена шаблона.
  • Мотивация (Motivation/Forces): ситуация с проблемой, в которой применим шаблон.
  • Область применимости (Applicability): ситуации, в которых шаблон полезен.
  • Структура (Structure): графическое представление шаблона. Могут использоваться UML, диаграммы взаимодействия и др.
  • Участники (Participants): список участников (классов, объектов) и их роли.
  • Взаимодействие (Collaboration): описание взаимодействия участников между собой.
  • Последствия (Consequences): результаты, побочные эффекты и компромиссы при использовании шаблона.
  • Реализация (Implementation): описание реализации шаблона.
  • Пример кода (Sample Code): иллюстрация использования в языке программирования.
  • Известные применения (Known Uses): реальные примеры использования шаблона.
  • Связанные шаблоны (Related Patterns): шаблоны, имеющие отношение к данному; различия и сходства.

Критика

Некоторые исследователи считают, что шаблоны проектирования свидетельствуют о нехватке определённых языковых средств (например, в Java или C++) — так, Питер Норвиг показал, что 16 из 23 шаблонов из книги Design Patterns (где большая часть примеров для C++) могут быть сведены или избыточны в Lisp или Dylan, благодаря широкой поддержке динамических возможностей[28]. Подобные наблюдения приводили Ханнеманн и Кицалес, реализовавшие те же шаблоны на языке с аспектно-ориентированным программированием (AspectJ) — оказалось, что для 17 из 23 шаблонов были устранены зависимости на уровне кода, а реализация упростилась[29]. См. также эссе Пола Грэма «Revenge of the Nerds»[30].

Чрезмерное и неадекватное применение шаблонов может привести к ненужному усложнению системы[31]. FizzBuzzEnterpriseEdition — шутливый пример чрезмерной сложности при избыточном использовании шаблонов[32].

Шаблон требует отдельной реализации в каждом проекте. Поскольку некоторые авторы считают это шагом назад по сравнению с повторным использованием компонентов, ведутся исследования по превращению шаблонов в компоненты. Бертран Мейер и Арно сумели полностью или частично реализовать две трети шаблонов как компоненты[33].

Для достижения гибкости шаблоны часто вводят дополнительную степень косвенности, что может усложнить проект и снизить производительность.

Отношения с другими понятиями

Шаблоны проектирования программного обеспечения имеют более мелкий масштаб по сравнению с архитектурными шаблонами и стилями: они нацелены на решение частных задач внутри компонентов или подсистем (например, Одиночка, Фабричный метод, Наблюдатель)[34][35][36].

Архитектурный шаблон — это обобщённое, многократно используемое решение проблем системного уровня: определяет общую структуру системы, взаимодействие компонентов, нефункциональные требования и качества системы. Такие шаблоны действуют на более высоком уровне абстракции и обычно решают архитектурные задачи. Граница между архитектурными шаблонами и архитектурными стилями иногда размыта. Пример: Circuit Breaker[34][35].[36]

Архитектурный стиль определяет высокоуровневую организацию системы, указывая, как компоненты структурированы, взаимодействуют и каким ограничениям подчинены. Часто включает типовую терминологию (компоненты, соединители), а также семантику системы на самом грубом уровне организации. Примеры: Многоуровневая архитектура, микросервисы, событийно-ориентированная архитектура[34][35].[36]

Примечания

Литература

  • Alexander, Christopher. A Pattern Language: Towns, Buildings, Construction / Christopher Alexander, Sara Ishikawa, Murray Silverstein … [и др.]. — New York : Oxford University Press, 1977. — ISBN 978-0-19-501919-3.
  • Alur, Deepak. Core J2EE Patterns: Best Practices and Design Strategies / Deepak Alur, John Crupi, Dan Malks. — 2-е. — Prentice Hall, Май 2003. — ISBN 978-0-13-142246-9.
  • Beck, Kent. Implementation Patterns. — Addison-Wesley, Октябрь 2007. — ISBN 978-0-321-41309-3.
  • Beck, Kent. Proceedings of the 18th International Conference on Software Engineering / Kent Beck, R. Crocker, G. Meszaros … [и др.]. — Март 1996. — P. 25–30.
  • Borchers, Jan. A Pattern Approach to Interaction Design. — John Wiley & Sons, 2001. — ISBN 978-0-471-49828-5.
  • Coplien, James O. Pattern Languages of Program Design / James O. Coplien, Douglas C. Schmidt. — Addison-Wesley, 1995. — ISBN 978-0-201-60734-5.
  • Coplien, James O. Pattern Languages of Program Design 2 / James O. Coplien, John M. Vlissides, Norman L. Kerth. — Addison-Wesley, 1996. — ISBN 978-0-201-89527-8.
  • Eloranta, Veli-Pekka. Designing Distributed Control Systems: A Pattern Language Approach / Veli-Pekka Eloranta, Johannes Koskinen, Marko Leppänen … [и др.]. — Wiley, 2014. — ISBN 978-1118694152.
  • Fowler, Martin. Analysis Patterns: Reusable Object Models. — Addison-Wesley, 1997. — ISBN 978-0-201-89542-1.
  • Fowler, Martin. Patterns of Enterprise Application Architecture. — Addison-Wesley, 2003. — ISBN 978-0-321-12742-6.
  • Freeman, Eric. Head First Design Patterns / Eric Freeman, Elisabeth Freeman, Kathy Sierra … [и др.]. — O'Reilly Media, 2004. — ISBN 978-0-596-00712-6.
  • Hohmann, Luke. Beyond Software Architecture / Luke Hohmann, Martin Fowler, Guy Kawasaki. — Addison-Wesley, 2003. — ISBN 978-0-201-77594-5.
  • Gabriel, Richard. Patterns of Software: Tales From The Software Community. — Oxford University Press, 1996. — P. 235. — ISBN 978-0-19-512123-0.
  • Gamma, Erich. Design Patterns: Elements of Reusable Object-Oriented Software / Erich Gamma, Richard Helm, Ralph Johnson … [и др.]. — Addison-Wesley, 1995. — ISBN 978-0-201-63361-0.
  • Hohpe, Gregor. Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions / Gregor Hohpe, Bobby Woolf. — Addison-Wesley, 2003. — ISBN 978-0-321-20068-6.
  • Holub, Allen. Holub on Patterns. — Apress, 2004. — ISBN 978-1-59059-388-2.
  • Kircher, Michael. Remoting Patterns: Foundations of Enterprise, Internet and Realtime Distributed Object Middleware / Michael Kircher, Markus Völter, Uwe Zdun. — John Wiley & Sons, 2005. — ISBN 978-0-470-85662-8.
  • Larman, Craig. Applying UML and Patterns. — Prentice Hall, 2005. — ISBN 978-0-13-148906-6.
  • Liskov, Barbara. Program Development in Java: Abstraction, Specification, and Object-Oriented Design / Barbara Liskov, John Guttag. — Addison-Wesley, 2000. — ISBN 978-0-201-65768-5.
  • Manolescu, Dragos. Pattern Languages of Program Design 5 / Dragos Manolescu, Markus Voelter, James Noble. — Addison-Wesley, 2006. — ISBN 978-0-321-32194-7.
  • Marinescu, Floyd. EJB Design Patterns: Advanced Patterns, Processes and Idioms. — John Wiley & Sons, 2002. — ISBN 978-0-471-20831-0.
  • Martin, Robert Cecil. Pattern Languages of Program Design 3 / Robert Cecil Martin, Dirk Riehle, Frank Buschmann. — Addison-Wesley, 1997. — ISBN 978-0-201-31011-5.
  • Mattson, Timothy G. Patterns for Parallel Programming / Timothy G. Mattson, Beverly A. Sanders, Berna L. Massingill. — Addison-Wesley, 2005. — ISBN 978-0-321-22811-6.
  • Shalloway, Alan. Design Patterns Explained, Second Edition: A New Perspective on Object-Oriented Design / Alan Shalloway, James R. Trott. — Addison-Wesley, 2001. — ISBN 978-0-321-24714-8.
  • Vlissides, John M. Pattern Hatching: Design Patterns Applied. — Addison-Wesley, 1998. — ISBN 978-0-201-43293-0.
  • Weir, Charles. Small Memory Software: Patterns for systems with limited memory / Charles Weir, James Noble. — Addison-Wesley, 2000. — ISBN 978-0-201-59607-6.