1. История и классификация паттернов проектирования

1.1. Что такое паттерн проектирования?

1.2. Классификация паттернов

Порождающие Структурные Поведенческие
  • Factory method
  • Abstract factory
  • Builder
  • Prototype
  • Singleton
  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy
  • Chain of responsibility
  • Command
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template method
  • Visitor

Источник: https://refactoring.guru/ru

1.3. Обозначения UML

Class (C) Имя класса <шаблонный_тип> + открытая_переменная: тип - закрытая_переменная # защищённая_переменная $ статичная_переменная + открытый_метод(): возвращаемый_тип - закрытый_метод() # защищённый_метод() $ статичный_метод() Interface (I) Интерфейс + метод1() + метод2() метод3() @ абстрактный_метод() A A B B A->B Наследование A от B (стрелка указывает на базовый класс) C C D D C->D Аггрегирование: C использует D (время жизни у обоих может быть разным) E E F F E->F Композиция: F принадлежит E (время жизни у обоих одинаковое) G G H H G->H G содержит массив объектов H 1+
ИмяКлассаШаблонный_типпеременная: типзакрытая_переменнаязащищённая_переменнаястатическая_переменнаяСекция переменныхоткрытый_метод(): возвращаемое_значениезакрытый_метод()защищённый_метод()статичный_метод()Секция методовИнтерфейсметод1()метод2()метод3()абстрактный_метод()ABCDEFGHнаследование A от B(стрелка указывает на базовый класс)аггрегирование: C использует D(например, в качестве параметра)композиция: F принадлежит E(например, в качестве переменной класса)G содержит массивобъектов H1+

2. Порождающие паттерны

2.1. Общие сведения

Создающие классы

Виртуальный конструктор — статический метод, заменяющий new и не зависящий от типа

3. Abstract factory (абстрактная фабрика)

AbstractFactorycreateA(): AcreateB(): BcreateC(): CFactory1createA() override: A...Factory2createA() override: A...
  • Фабрика — это просто набор виртуальных конструкторов
  • A, B, C — это интерфейсы
  • Приходится писать не только сами объекты, но и фабрику
Пример

Фабрика по созданию окон, кнопок, полей ввода в зависимости от менеджера графики (X11, cairo, GtK, Qt, Mate, Windows, MacOS, …)

4. Factory method (фабричный метод)

SomeObject...createObj(): IObjIObjObj1...Obj2...
  • Частный случай абстрактной фабрики
  • IObj — это интерфейс
  • SomeObject не является интерфейсом
  • Дальнейшее расширение объекта фабричными методами create приводит к созданию фабрики
Пример

Диалоговое окно, у которого есть фабричный метод createButton, возвращающий кнопку в зависимости от графического менеджера

4.1. Builder (строитель)

BuilderbuildPartA()buildPartB()buildPartC()Builder1buildPartA() override...getProduct(): Product1Builder2buildPartA() override...getProduct(): Product2DirectormakeVariant1()makeVariant2()
  • Замена конструктора с 100500+ параметрами
  • Builder похож на абстрактную фабрику и компоновщик
  • Director похож на реализации стратегий
  • Разделение Product на части
Пример

Создание разных видов диалоговых окон в зависимости от графического менеджера

4.2. Prototype (прототип)

Prototypeclone(): PrototypeObject1...clone(): PrototypeObject2...clone(): Prototype
  • Конструктор копирования
  • Оператор =
  • "Глубокая" копия объекта
  • Должны наследовать интерфейс Prototype
Пример

Клонирование сложного объекта в графическом редакторе

4.3. Singleton (одиночка)

Singletoninstance: SingletonSingleton()getInstance(): Singleton
  • Чтобы не писать глобальную переменную
  • Проблема гонок во время создания
Пример

Системный логгер

5. Стуктурные паттерны

5.1. Общие сведения

Определящие структуру класса

5.2. Adapter (адаптер)

ClientInterfacemethod1()method2()Adaptermethod1()method2()Objectfunction()another()
  • Переопределение интерфейса
  • Клиент должен выделить отдельно ClientInterface
Пример

std::queue

5.3. Bridge (мост)

BridgeComplexAdaptermethod1()method2()method3()Targetfunction()another()Implementation1function()another()Implementation2function()another()Refinementmethod4()method5()
  • Усложнённый адаптер (два адаптера)
  • Клиент использует ComplexAdapter или одно из его уточнений Refinement
  • Одновременное развитие ComplexAdapter и Target
Пример

Приложение для отрисовки компонент использует абстракцию с примитивами, которые, в свою очередь, рисуются благодаря интерфейсу графического менеджера. Последний может быть реализован по-разному (OpenGL, например)

5.4. Composite (компоновщик)

SomeInterfacemethod1()method2()Object...method1()method2()Compositionobjects: SomeInterface[]method1()method2()vectortypeoperator[](): type&begin(): vector::iteratorend(): vector::interfacefor object in objects:object.method1()
  • Ведёт себя как один объект
  • Все вызовы делегирует хранящимся в нём объектам
  • Иногда может агрегировать объекты
  • Хранимые объекты должны быть того же типа SomeInterface, что и компоновщик
Пример

Группировка объектов в графическом редакторе

5.5. Decorator (декоратор)

Componentmethod()Decoratorwrapee: Component&method()decorate()ComponentA...method()DecoratorA...decorate()DecoratorB...decorate()wrapee.method()decorate()
  • Повторяет интерфейс оборачиваемого объекта
  • Все вызовы делегируются оборачиваемым объектам, вызывая дополнительный код до или после операций объекта
Пример

Рисование рамки вокруг символов, умляуты на глифах

5.6. Facade (фасад)

Facade...method1()method2()SubSystem1func()another()SubSystem2do()make()SubSystem3some()
  • Фасад необходим для упрощения использования несколькими системами классов или фреймворков
  • Реализует новый интерфейс
  • Содержит в себе много различных классов или подсистем
  • Частный случай фасада — адаптер
  • Излишняя осведомлённость обо всех объектах
  • Склонен становиться "божественным объектом"
Пример

Почти все объекты вокруг

5.7. Flyweight (легковес)

Clientsome()method()VeryComplexObjectobj1: Flyweightobj2: Obj2obj3: Obj3...Flyweightcache: Obj1operation()AbstractFactoryobjects: map<some types, Flyweight>getObject(some types): Flyweight&100500+~100
  • Flyweight какая-либо повторяющая сущность объектов
  • Экономия памяти
  • Разделение объекта на более маленькие куски
  • Не подходит для уникальных объектов
Пример

Пвторяющиеся элементы объектов в играх

5.8. Proxy (заместитель)

IObjectsome()method()Proxycache: IObjectsome()method()Service...some()method()
  • Адаптер и декоратор в одном лице, а также фасад
  • В основном используется для кеширования результатов
  • В качестве кеша может использоваться сложный сервис (с запросами по сети, сохранениями файлов и т.д.)
Пример

std::vector<bool>::reference

6. Поведенческие паттерны

6.1. Общие сведения

Определяют взаимодействие классов

6.2. Chain of responsibility (цепочка обязанностей)

RequestIProcessorsuccessor: IProcessorprocess(): boolProcessor1process()Processor2process()Processor3process()EndProcessorprocess()
  • Каждый в цепочке либо обрабатывает запрос, либо передаёт дальше
  • В случае обработки запроса цепочка может быть либо прервана, либо продолжена
  • Реализация "луковицей"
  • Возможна реализация через брокера и пайплайн (компоновщик)
  • EndProcessor может потерять запрос
Пример

Общение с техподдержкой

6.3. Command (команда)

Commandexecute()Pastecache: data&execute()Copydataexecute()Clientactions: Command[]send()Serverreceive()apply()Command
  • Отложенный вызов
  • Лямбда-функции
Пример

Транзакции в базе данных, std::bind

6.4. Iterator (итератор)

IteratorTypeoperator++(): self&operator*(): Type&IterableTypecollection: Type[]begin()end()InputIteratorTypeoperator++(): self&operator*(): Type&OutputIteratorTypeoperator++(): nulloperator*(): self&operator=(): self&
  • Придаёт объекту свойства контейнера
  • Похож на композицию, может использоваться в цепочке обязанностей
  • Могущество спрятано в операторах ++, * и =
  • Разные способы обхода одного и того же множества
Пример

std::vector::iterator, обход графа в ширину и глубину — два типа итератора

6.5. Mediator (посредник)

Mediatornotify(sender)MainonEventA()onEventB()onEventC()notify(sender)ComponentAoperationA()ComponentBoperationB()ComponentCoperationC()
  • Main содержит всю бизнес-логику
  • Компоненты общаются между собой только через посредника
  • Все компоненты могут использоваться повторно в других программах, но сам посредник не может
  • Проблема "божественного объекта"
Пример

Диспетчерская, диалоговое окно с компонентами

6.6. Memento (снимок)

Object1somefieldssave(): Mementorestore(Memento&)Object2anothersave(): Mementorestore(Memento&)MementoMemento1somefieldsMemento2another
  • Схож с прототипом
  • Не нарушает инкапсуляцию
Пример

Сериализация

6.7. Observer (наблюдатель)

Publisherobservers: Observer[]add(Observer)remove(Observer)notify()Observerupdate(publisher)Observer1...update(publisher)Observer2...update(publisher)*
  • Наблюдатели подписываются на события, генерируемые Publisher
  • Оповещения происходят в случайном порядке, в отличие от цепочки обязанностей
  • Похож на посредника, но Publisher ничего не знает о подписчиках
Пример

Почтовая рассылка

6.8. State (состояние)

ContextchangeState(State)method1()method2()Statemethod1()method2()State1method1()method2()State2method1()method2()State3method1()method2()doSomething()context.changeState(new State3)
  • Замена большого switch-case
  • Context агрегирует объекты State
  • Каждый State может переключать состояние в Context
  • State-ы кое-что знают друг о друге
Пример

TCP-соединение, переключение окон на экране смартфона

6.9. Strategy (стратегия)

Strategydo()Strategy1...do()Strategy2...do()ContextsetStrategy(Strategy)make()
  • Все стратегии делают одно и то же разными способами
  • Context схож с мостом и немного адаптером
  • Пользователь выбирает стратегии, Context их только использует
Пример

Построение маршрута из точки A в точку B для разных средств передвижения

6.10. Template method (шаблонный метод)

AlgorithmtemplateMethod()step1()step2()step3()Step1step1()step2()Step2step1()step3()Algorithm1Algorithm2
  • Множественное наследование
  • Алгоритм разбивается на шаги, которые реализованы отдельно
  • Все шаги комбинируются и жёстко прописаны в templateMethod
  • templateMethod не переопределяется
  • Схож со стратегиями, где композиция заменена наследованием
  • Невозможно переопределять шаги во время исполнения
Пример

Общий ИИ для разных персонажей игры

6.11. Visitor (посетитель)

Visitorvisit(Object1)visit(Object2)Visitor1visit(Object1)visit(Object2)Visitor2visit(Object1)visit(Object2)Visitor3visit(Object1)visit(Object2)Object1...accept(self&)Object2...accept(self&)visitor = new Visitor1foreach obj in Objectsobj.visit(visitor)
  • Обход всех компонентов рекурсивно
  • Совмещает работу компоновщика и итератор
  • Сколько объектов Objects, столько должно быть функций visit в интерфейсе Visitor
  • Каждый Visitor нужен для реализации какой-то части логики
Пример

Рекурсивная сериализация в разные форматы (каждый формат — это новый объект Visitor)