В трех частях статьи мы последовательно рассмотрим 23 шаблона проектирования, которые впервые были перечислены в книге «Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения». Описание каждого шаблона включает структуру, объяснение, рекомендации по использованию и пример практического применения. Первая часть посвящена поведенческим шаблонам, вторая – структурным, а третья – порождающим.
Основные типы шаблонов делятся на:
Поведенческие – используются для управления алгоритмами, отношениями и обязанностями между объектами. Примеры поведенческих шаблонов:
- Цепочка обязанностей
- Команда
- Интерпретатор
- Посредник
- Хранитель
- Наблюдатель
- Состояние
- Шаблонный метод
- Посетитель
Структурные – используются для формирования больших объектных структур между многочисленными разрозненными объектами. Основные структурные шаблоны:
- Адаптер
- Мост
- Компоновщик
- Декоратор
- Фасад
- Легковес
- Заместитель
Порождающие – используются для построения объектов таким образом, чтобы они могли быть отделены от реализующей их системы. Главные порождающие шаблоны:
- Абстрактная фабрика
- Строитель
- Фабричный метод
- Прототип
- Одиночка
Поведенческий шаблон «Цепочка обязанностей»
Назначение
Позволяет нескольким объектам (вместо одного) обрабатывать запросы, объединяя объекты в цепочку.
Когда использовать
- Если запрос может быть обработан несколькими объектами, и обработчик не обязательно должен быть конкретным объектом.
- Набор объектов должен быть способен обработать запрос с обработчиком, определяемым во время выполнения.
- Если приемлемый потенциальный результат – оставить запрос без обработки.
Описание
Все обработчики реализуют один абстрактный класс Handler, который содержит ссылку на самого себя (successor) для делегирования обязанностей по обработке следующему обработчику в цепочке. Реализация метода handleRequest() по умолчанию выполняет такую делегацию.
Пример
Этот шаблон используется в некоторых языках для обработки исключений. Когда в методе возникает исключение, среда выполнения проверяет, есть ли в методе механизм для обработки исключения или его следует передать в стек вызовов. При передаче в стек вызовов процесс повторяется до тех пор, пока не будет найден код для обработки исключения или пока не останется родительских объектов, которым можно передать запрос.
Поведенческий шаблон «Команда»
Назначение
Преобразует запросы в объекты, позволяя передавать их как аргументы при вызове методов, ставить запросы в очередь, сохранять историю, а также поддерживать отмену операций.
Когда использовать
- Если нужна функциональность обратного вызова.
- Запросы должны обрабатываться в разное время или в разном порядке.
- Необходимо сохранять историю запросов.
- Если вызывающая сторона должна быть отделена от объекта, обрабатывающего вызов.
Структура
Клиент (Client) создает объекты конкретных команд. Отправитель (Invoker) хранит ссылку на объект команды и обращается к нему, если нужно выполнить какое-то действие. Отправитель работает с командами только через их общий интерфейс.
Команда (Command) описывает общий для всех конкретных команд (Concrete Command) интерфейс. Отправитель не знает, какую конкретно команду использует, так как получает готовый объект команды от клиента. Получатель (Receiver) содержит бизнес-логику программы.
Пример
Очереди заданий широко используются для облегчения асинхронной обработки алгоритмов. При использовании шаблона команды можно передать очереди заданий для обработки некую функцию таким образом, чтобы очередь не знала о фактическом результате ее выполнения. Объект команды, который ставится в очередь, реализует свой конкретный алгоритм в рамках интерфейса, ожидаемого очередью.
Поведенческий шаблон «Интерпретатор»
Назначение
Определяет представление для грамматики, а также механизм для понимания и действий с грамматикой.
Когда использовать
- Есть грамматика, которую нужно интерпретировать и которая может быть представлена в виде больших синтаксических деревьев.
- Грамматика проста.
- Эффективность не важна.
- Желательно отделить грамматику от основных выражений.
Структура
Абстрактное выражение (Abstract Expression) объявляет метод Interpret(). Для каждого символа грамматики создается свой объект Terminal Expression (терминальное выражение). Для каждого отдельного правила грамматики создается свой объект Nonterminal Expression (нетерминальное выражение)
Контекст (Context) содержит общую информацию, может использоваться для сохранения состояния операций и последующего доступа к сохраненному состоянию. Клиент (Client) строит предложения с данной грамматикой в виде абстрактного синтаксического дерева, узлами которого являются объекты Terminal Expression и Nonterminal Expression.
Пример
Игры с текстовыми командами, популярные в 1980-х годах. Во многих из них были простые команды, например, «шаг вниз» , которые позволяли перемещаться по игре. Эти команды могли быть вложены друг в друга так, чтобы можно было изменить их значение. Например, результат выполнения go in «войти» отличался от go up «подняться». Создавая иерархию команд на основе команды и классификатора (нетерминальных и терминальных выражений), приложение могло легко соотнести множество вариантов команд с нужными действиями.
Поведенческий шаблон «Итератор»
Назначение
Позволяет получить доступ к элементам составного объекта без доступа к его внутреннему представлению.
Когда использовать
- Требуется доступ к элементам без открытия доступа ко всему представлению.
- Необходимы множественные или параллельные обходы элементов.
- Нужен единый интерфейс для обхода.
- Существуют небольшие различия между деталями реализации различных итераторов.
Структура
Итератор (Iterator) описывает интерфейс для доступа и обхода элементов коллекции. Конкретный итератор (Concrete Iterator) реализует алгоритм обхода какой-то конкретной коллекции.
Коллекция (Aggregate) описывает интерфейс получения итератора из коллекции. Конкретная коллекция (Concrete Aggregate) возвращает новый экземпляр определенного конкретного итератора, связав его с текущим объектом коллекции. Клиент работает со всеми объектами через интерфейсы коллекции и итератора.
Пример
Реализация шаблона итератора в Java позволяет пользователям обращаться к различным типам наборов данных, не беспокоясь о внутренней реализации коллекции. Поскольку клиенты просто взаимодействуют с интерфейсом итератора, коллекциям остается определить подходящий итератор самостоятельно. Некоторые из них предоставляют полный доступ к базовому набору данных, в то время как другие могут ограничивать определенные функции, например, удаление элементов.
Поведенческий шаблон «Посредник»
Назначение
Обеспечивает слабое зацепление, инкапсулируя способ взаимодействия и связи между разрозненными наборами объектов. Позволяет действиям каждого набора объектов изменяться независимо друг от друга.
Когда использовать
- Связь между наборами объектов хорошо определена и сложна.
- Существует слишком много взаимосвязей, и необходима общая точка управления или связи.
Структура
Посредник (Mediator) определяет интерфейс для обмена информацией с объектами Коллеги (Colleague). Конкретный посредник координирует действия объектов Коллеги. Каждый класс Коллеги знает о своем объекте Посредник, все Коллеги обмениваются информацией только с Посредником, не напрямую.
Пример
Приложение для управления почтовой рассылкой отслеживает, кто и на какие новости подписан, и предоставляет единую точку доступа, через которую администратор рассылки может отправлять сообщения всем или отдельными подписчиками.
Поведенческий шаблон «Хранитель»
Назначение
Позволяет захватывать и извлекать внутреннее состояние объекта, чтобы его можно было восстановить позже, не нарушая инкапсуляции.
Когда использовать
- Если внутреннее состояние объекта должно быть сохранено и восстановлено позднее.
- Внутреннее состояние не может быть раскрыто интерфейсами без раскрытия реализации.
- Границы инкапсуляции должны быть сохранены.
Структура
Создатель (Originator) делает снимки своего состояния и может воспроизводить прошлое состояние, если передать в него готовый снимок. Хранитель (Memento) — простой объект данных, содержащий состояние создателя. Опекун (Caretaker) знает, когда делать снимок создателя и когда его нужно восстанавливать.
Пример
С помощью «Хранителя» можно реализовать функцию отмены действия. Сериализуя и десериализуя состояние объекта до того, как произойдет изменение, можно сохранить снимок для последующего восстановления, если пользователь решит отменить операцию.
Поведенческий шаблон «Наблюдатель»
Назначение
Позволяет одному или нескольким объектам получать уведомления об изменениях состояния других объектов в системе.
Когда использовать
- Если изменение состояния одного или нескольких объектов должно вызывать реакцию других объектов.
- Нужно управлять массовой рассылкой.
- Подписчики не платят за получение уведомлений.
Структура
Наблюдатель (Observer) передает запрос одновременно всем заинтересованным получателям (Concrete Subject), но позволяет им динамически подписываться или отписываться от таких оповещений.
Пример
Этот шаблон используется почти в каждом графическом интерфейсе. Когда пользователь вызывает событие, например, нажатие кнопки, элемент управления перебирает все зарегистрированные наблюдатели и посылает каждому из них уведомление.
Поведенческий шаблон «Состояние»
Назначение
Связывает обстоятельства объекта с его поведением, позволяя объекту вести себя по-разному в зависимости от его внутреннего состояния.
Когда использовать
- Если поведение объекта должно зависеть от его состояния.
- Поведение объекта с его состоянием связывают сложные условия.
- Переходы между состояниями должны быть явными.
Структура
Контекст (Context) хранит ссылку на объект состояния и передает ему часть работы, зависящей от состояний. Состояние (State) описывает общий интерфейс для всех конкретных состояний. Конкретные состояния (Concrete State) реализуют поведения, связанные с определенным состоянием контекста.
Пример
Электронное письмо может иметь различные состояния, каждое из которых влияет на то, как объект обрабатывает различные функции. Если состояние «не отправлено», то вызов send() отправит сообщение, а вызов recallMessage() либо выдаст ошибку, либо ничего не сделает. Однако если состояние «отправлено», то вызов send() либо выдаст ошибку, либо ничего не сделает, а вызов recallMessage() попытается отправить уведомление получателям. Чтобы избежать условных выражений в большинстве или во всех методах, существует несколько объектов состояния, которые обрабатывают реализацию в соответствии со своим конкретным состоянием. Вызовы внутри объекта Email будут передаваться соответствующему объекту состояния для обработки.
Поведенческий шаблон «Стратегия»
Назначение
Определяет семейство схожих алгоритмов и помещает каждый из них в собственный класс, благодаря чему алгоритмы можно взаимозаменять во время исполнения программы.
Когда использовать
- Если единственное различие между многочисленными родственными классами заключается в их поведении.
- Требуется несколько версий или вариаций алгоритма.
- Алгоритмы получают доступ или используют данные, которые не должны быть доступны вызывающему коду.
- Поведение класса должно определяться во время выполнения.
- Условные операторы сложны и их трудно поддерживать.
Структура
Контекст (Context) хранит ссылку на объект конкретной стратегии и работает с ним через общий интерфейс стратегий. Стратегия (Strategy) определяет интерфейс, общий для всех вариаций алгоритма. Конкретные стратегии (Concrete Strategy) реализуют различные вариации алгоритма.
Пример
При импорте данных в новую систему на основе набора данных могут выполняться различные алгоритмы проверки. Настроив импорт на использование стратегий, можно убрать условную логику, определяющую, какой набор проверки запускать, и отделить импорт от кода проверки. Это позволит динамически вызывать одну или несколько стратегий во время импорта.
Поведенческий шаблон «Шаблонный метод»
Назначение
Определяет структуру алгоритма, позволяя реализующим классам определять фактическое поведение.
Когда использовать
- Если необходима единая абстрактная реализация алгоритма.
- Общее поведение среди подклассов должно быть локализовано в общем классе.
- Родительские классы должны иметь возможность единообразно вызывать поведение в своих подклассах.
- Большинство или все подклассы должны реализовать поведение.
Структура
Абстрактный класс (Abstract Class) определяет шаги алгоритма и содержит шаблонный метод, состоящий из вызовов этих шагов. Конкретный класс (Concrete Class) переопределяет некоторые (или все) шаги алгоритма. Конкретные классы не переопределяют сам шаблонный метод.
Пример
Родительский класс InstantMessage будет включать все методы, необходимые для обработки отправки сообщения. Однако фактическая сериализация данных для отправки может отличаться в зависимости от реализации. Видеосообщение и обычное текстовое сообщение потребуют различных алгоритмов для правильной сериализации данных. Подклассы InstantMessage могут предоставить свою собственную реализацию метода сериализации, позволяя родительскому классу работать с ними без понимания деталей их реализации.
Поведенческий шаблон «Посетитель»
Назначение
Позволяет применять одну или несколько операций к набору объектов во время выполнения, отделяя операции от структуры объекта.
Когда использовать
- Если над структурой объекта выполняется множество несвязанных операций.
- Структура объекта не должна меняться, но операции, выполняемые над ней, могут.
- Операции должны выполняться над конкретными классами структуры объекта.
- Раскрытие внутреннего состояния или операций структуры объекта допустимо.
- Операции должны быть способны работать с несколькими структурами, реализующими одни и те же интерфейсные наборы.
Структура
Посетитель (Visitor) описывает общий интерфейс для всех типов посетителей. Он объявляет набор методов, отличающихся типом входящего параметра, которые нужны для запуска операции для всех типов конкретных элементов.
Конкретные посетители (Concrete Visitor) реализуют какое-то особенное поведение для всех типов элементов. Элемент (Element) описывает метод принятия посетителя. Конкретные элементы (Concrete Element)реализуют методы принятия посетителя. Клиентом (Client) обычно является коллекция или сложный составной объект.
Пример
Расчет налогов в разных системах налогообложения по наборам счетов-фактур потребует множества различных вариантов логики расчета. Реализация шаблона «Посетитель» позволяет отделить логику от счетов-фактур. Это позволяет «посетить» иерархию элементов с помощью кода вычисления, который затем может применить соответствующие налоговые ставки. Сменить систему налогообложения в этом случае просто, достаточно сменить «Посетителя».
Комментарии