Шаблон проектирования
Шаблон проектирования (англ. 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. | Да | Да | Да |
| Мост | Отделяет абстракцию от реализации, позволяя им изменяться независимо. | Да | Да | Да |
| Компоновщик | Объединяет объекты в древовидные структуры для представления иерархии «часть-целое». Позволяет клиентам единообразно работать как с отдельными объектами, так и с их композициями. | Да | Да | Да |
| Декоратор | Динамически добавляет объекту новые обязанности, сохраняя прежний интерфейс. Более гибкая альтернатива наследованию. | Да | Да | Да |
| Делегирование | Расширяет класс через композицию вместо наследования. Объект обрабатывает запрос, делегируя действие другому объекту. | Да | Да | Да |
| Объект расширения | Добавляет функциональность иерархии без изменения самой иерархии. | Да | Да | Да |
| Фасад | Предоставляет унифицированный интерфейс к группе интерфейсов подсистемы, упрощая её использование для клиентов. | Да | Да | Да |
| Приспособленец | Эффективно поддерживает большое число похожих объектов с помощью совместного использования состояния. | Да | Да | Да |
| Фронт-контроллер | Централизованная точка входа для обработки запросов (часто в веб-приложениях). | Да | Да | |
| Маркерный интерфейс | Пустой интерфейс для маркировки класса метаданными. | Да | Да | 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.