Структурные паттерны проектирования

Содержание курса

1. Intro (8:48)
2. Strategy (Стратегия) (14:56)
3. Observer (Наблюдатель) (12:58)
4. Decorator (Декоратор) (16:18)
5. Simple Factory (Простая фабрика) (12:44)
6. Factory Method (Фабричный метод) (10:08)
7. Abstract Factory (Абстрактная фабрика) (10:05)
8. Singleton (Синглтон) (10:30)
9. Command (Команда) (24:34)
10. Adapter (Адаптер) (9:23)
11. Facade (Фасад) (11:13)
12. Template Method (Шаблонный метод) (20:02)
13. Iterator (Итератор) (26:10)
14. Composite (Компоновщик) (12:18)
15. State (Состояние) (18:08)
16. Proxy (Прокси) (15:49)
17. Builder (Строитель) (14:06)
18. Chain of responsibility (Цепочка ответственностей) (12:52)
19. Outro (0:43)

Общая продолжительность курса 4 часа 06 минут и 49 секунда.

Поехали!

Паттерн 123 на бычьем тренде

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


Паттерн 123 на бычьем тренде.

Мы видим на графике три точки 1, 2, 3. Первая точка представляет собой новый минимум на графике. После некоторого тренда рынок образовал новый минимум, далее он пошел вверх и сформировал новую вершину в точке 2. Затем началась коррекция, которая в своем завершении породила новый минимум в точке 3, находящейся выше точки 1.

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

Вот, в принципе, и все. Этот паттерн, как и многие другие формации, основывается на определении тренда. Напоминаем правило нисходящего тренда:

Каждый новый минимум должен быть ниже предыдущего. Как только это правило нарушается, и мы видим пробой последнего максимума, это значит, что на графике имеется паттерн 123, а пробой в точке 2 помогает войти в сделку. Таким образом, повторимся, все основывается на определении тренда.

Вот обязательно смотрите видео:

Z-паттерн

Согласно этой модели, взгляд пользователя при изучении страницы последовательно проходит точки 1,2,3 и 4. Если мы посмотрим на результаты исследований Якоба Нильсена (известного UI/UX специалиста, автора 10 принципов упешного интерфейса),то видно, что в рамках первого экрана поведение пользователей описывается именно Z-паттерном.

Секторы 1, 2 и 3 получают больше всего внимания, в то время как 4 почти не просматривается: в соответствии с исследованием  Нильсена дальше пользователь идет вдоль вертикальной оси F. И на тепловой карте это четко видно.

Недостаток Z-паттерна такой же — он описывает ограниченное количество пользовательских сценариев: текстовый контент, монотонная сетка— этого явно недостаточно для анализа модели пользовательского поведения.

Еще два паттерна, которые тесно связаны с F и Z моделями — зигзаг (1) и золотой треугольник (2).

Зигзаг

Золотой треугольник

Они хорошо вписываются в модель Якоба Нильсена и описывают поведение пользователя при контакте с текстовым контентом.

Но интернет сегодня — не только тексты. Я расскажу о диаграмме Гутенберга — паттерне, который наиболее полно описывает поведение пользователя при просмотре страниц сайта.

Шардирующие функции

Дан пользовательский запрос Req. Как выяснить, какой из шардов S с номерами от 0 до 9 должен обработать запрос?

Задача шардирующей функции — определить данное соответствие. Шардирующие функции похожи на хеш-функции, с которыми вы наверняка уже сталкивались, например, при изучении ассоциативных массивов. Действительно, хеш-таблицу в виде массива цепочек можно считать примером шардированного сервиса. Для заданных Req и Shard шардирующая функция должна установить между ними соответствие вида:

Shard = ShardingFunction(Req)

Шардирующая функция часто реализуется с использованием хеш-функции и оператора взятия остатка от деления (%). Хешфункции преобразуют произвольные цепочки байтов в целые числа фиксированной длины.

Хеш-функция имеет две важные с точки зрения шардинга характеристики:

Детерминированность — одинаковые цепочки байтов на входе должны порождать одинаковый результат на выходе.

Равномерность — значения хеш-функции должны иметь равномерное распределение.

Для шардированного сервиса первостепенное значение имеют детерминированность и равномерность хеш-функции.

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

Равномерность хеш-функции обеспечивает равномерное распределение нагрузки между шардами.

Проблема

Вы решили написать приложение-навигатор для путешественников. Оно должно показывать красивую и удобную карту, позволяющую с лёгкостью ориентироваться в незнакомом городе.

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

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

Через некоторое время выяснилось, что некоторые люди предпочитают ездить по городу на общественном транспорте. Поэтому вы добавили и такую опцию прокладывания пути.

Но и это ещё не всё. В ближайшей перспективе вы хотели бы добавить прокладывание маршрутов по велодорожкам. А в отдалённом будущем — интересные маршруты посещения достопримечательностей.

Код навигатора становится слишком раздутым.

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

Любое изменение алгоритмов поиска, будь то исправление багов или добавление нового алгоритма, затрагивало основной класс. Это повышало риск сделать ошибку, случайно задев остальной работающий код.

Принцип работы

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

Формирование модели бычьего рынка

После падения цены и образования нового минимального экстремума (точка 1) наблюдается рост рынка и формирование новой вершины фигуры (точка 2). Коррекция тренда оканчивается в точке 3, где образуется новое минимальное значение рынка. Этот минимум всегда выше предыдущего показателя.

Бычья фигура

Большинство трейдеров ошибочно полагает, что при образовании пин-бара в точке 2 продолжится нисходящий тренд. В этот момент игроки выставляют stop loss на одной линии с хвостом свечи. Однако разворот рынка наблюдается в точке 3 (стоп лоссы выбиваются), и цена идет вверх.

Формирование модели медвежьего движения

В случае с медвежьим рынком цена до определенного момента будет двигаться вверх и достигнет своего максимума – так появится точка 1 на графике индикатора. После этого можно наблюдать неизбежный откат и образование точки 2 – нового минимума.

Разворот на продажу

Затем наблюдается рост рынка, но цена не превышает значения 1. В этот момент образуется точка 3 – новое значение максимума. Цена пробивает горизонтальную линию и начинает свое движение вниз, что говорит о начале нового тренда – медвежьего. Это сигнал к заключению соответствующей сделки.

Видео Паттерн 123 – секреты прибыльного трейдинга:

Датчики готовности для балансировщика нагрузки

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

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

Многие приложения требуют некоторого времени на инициализацию, прежде чем они смогут обслуживать пользовательские запросы. Им, возможно, надо подключиться к базе данных, загрузить надстройки либо загрузить файлы из сети. Во всех этих случаях контейнеры исправны, но не готовы.

При построении приложения, использующего паттерн Replicated Service, не забывайте предусмотреть специальный URL, реализующий проверку готовности.

Основные шаблоны программирования

Фундаментальные

Шаблон делегирования ( Delegation pattern ) — Объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту.
Шаблон функционального дизайна ( Functional design ) — Гарантирует, что каждый модуль компьютерной программы имеет только одну обязанность и исполняет её с минимумом побочных эффектов на другие части программы.Неизменяемый интерфейс ( Immutable interface ) — Создание неизменяемого объекта.
Интерфейс ( Interface ) — Общий метод для структурирования компьютерных программ для того, чтобы их было проще понять.Интерфейс-маркер ( Marker interface ) — В качестве атрибута (как пометки объектной сущности) применяется наличие или отсутствие реализации интерфейса-маркера. В современных языках программирования вместо этого могут применяться атрибуты или аннотации.Контейнер свойств ( Property container ) — Позволяет добавлять дополнительные свойства для класса в контейнер (внутри класса), вместо расширения класса новыми свойствами.Событийный шаблон ( Event channel ) — Расширяет шаблон Publish/Subscribe, создавая централизованный канал для событий. Использует объект-представитель для подписки и объект-представитель для публикации события в канале. Представитель существует отдельно от реального издателя или подписчика. Подписчик может получать опубликованные события от более чем одного объекта, даже если он зарегистрирован только на одном канале.

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

Порождающие шаблоны ( Creational ) — шаблоны проектирования, которые абстрагируют процесс инстанцирования. Они позволяют сделать систему независимой от способа создания, композиции и представления объектов. Шаблон, порождающий классы, использует наследование, чтобы изменять инстанцируемый класс, а шаблон, порождающий объекты, делегирует инстанцирование другому объекту.Абстрактная фабрика ( Abstract factory ) — Класс, который представляет собой интерфейс для создания компонентов системы.Строитель ( Builder ) — Класс, который представляет собой интерфейс для создания сложного объекта.Фабричный метод ( Factory method ) — Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать.Отложенная инициализация ( Lazy initialization ) — Объект, инициализируемый во время первого обращения к нему.Пул одиночек ( Multiton ) — Гарантирует, что класс имеет поименованные экземпляры объекта и обеспечивает глобальную точку доступа к ним.Объектный пул ( Object pool ) — Класс, который представляет собой интерфейс для работы с набором инициализированных и готовых к использованию объектов.Прототип ( Prototype ) — Определяет интерфейс создания объекта через клонирование другого объекта вместо создания через конструктор.Получение ресурса есть инициализация ( Resource acquisition is initialization (RAII) ) — Получение некоторого ресурса совмещается с инициализацией, а освобождение — с уничтожением объекта.Одиночка ( Singleton ) — Класс, который может иметь только один экземпляр.

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

Структурные шаблоны ( Structural ) определяют различные сложные структуры, которые изменяют интерфейс уже существующих объектов или его реализацию, позволяя облегчить разработку и оптимизировать программу.Адаптер ( Adapter / Wrapper ) — Объект, обеспечивающий взаимодействие двух других объектов, один из которых использует, а другой предоставляет несовместимый с первым интерфейс.Мост ( Bridge ) — Структура, позволяющая изменять интерфейс обращения и интерфейс реализации класса независимо.Компоновщик ( Composite ) — Объект, который объединяет в себе объекты, подобные ему самому.Декоратор или Обёртка ( Decorator ) или ( Wrapper ) — Класс, расширяющий функциональность другого класса без использования наследования.Фасад ( Facade ) — Объект, который абстрагирует работу с несколькими классами, объединяя их в единое целое.Единая точка входа ( Front controller ) — Обеспечивает унифицированный интерфейс для интерфейсов в подсистеме. Front Controller определяет высокоуровневый интерфейс, упрощающий использование подсистемы.Приспособленец ( Flyweight ) — Это объект, представляющий себя как уникальный экземпляр в разных местах программы, но по факту не являющийся таковым.Заместитель ( Proxy ) — Объект, который является посредником между двумя другими объектами, и который реализует/ограничивает доступ к объекту, к которому обращаются через него.

Ведение журналов

Как и в случае с мониторингом, системы очень неоднородно журналируют данные. Системы могут разделять журналы на различные уровни, например debug, info, warning и error, каждый из которых записывается в отдельный файл. Некоторые просто выводят информацию в потоки stdout или stderr. Это особенно критично в случае контейнеризованных приложений, когда обычно ожидается, что контейнеры выводят информацию в поток stdout, так как именно его содержимое доступно при выполнении команд docker logs или kubectl logs

Усложняет ситуацию и то, что журналируемая информация в общем случае имеет структурированные элементы, например дату и время записи, но эти сведения сильно различаются для разных реализаций библиотек журналирования (например, для встроенного в Java средства журналирования и пакета glog в Go).

Adapter помогает предоставить модульную, повторно используемую архитектуру для ведения журналов.

Контейнер приложения может вести журнал в файле, а контейнер-адаптер будет перенаправлять его содержимое в поток stdout. Разные контейнеры приложения могут вести журналы в разных форматах, а контейнер-адаптер может преобразовывать эти данные в общее структурированное представление, которым сможет воспользоваться агрегатор журналов. Адаптер и в данном случае на основе неоднородной среды приложений создает однородную среду общих интерфейсов.

Одна из задач адаптера — привести показатели журнала к стандартному набору событий. Разные приложения имеют разные форматы выходных данных, но, чтобы привести их к однородному формату, можно задействовать стандартный инструмент журналирования, развернутый в виде адаптера.

Строитель (Builder)

Википедия гласит:

Пример из жизни: Представьте, что вы пришли в McDonalds и заказали конкретный продукт, например, БигМак, и вам готовят его без лишних вопросов. Это пример простой фабрики. Но есть случаи, когда логика создания может включать в себя больше шагов. Например, вы хотите индивидуальный сэндвич в Subway: у вас есть несколько вариантов того, как он будет сделан. Какой хлеб вы хотите? Какие соусы использовать? Какой сыр? В таких случаях на помощь приходит шаблон «Строитель».

Простыми словами: Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.

Давайте я покажу на примере, что такое «Телескопический конструктор». Когда-то мы все видели конструктор вроде такого:

Как вы можете заметить, количество параметров конструктора может резко увеличиться, и станет сложно понимать расположение параметров. Кроме того, этот список параметров будет продолжать расти, если вы захотите добавить новые варианты. Это и есть «Телескопический конструктор».

Перейдем к примеру в коде. Адекватной альтернативой будет использование шаблона «Строитель». Сначала у нас есть , который мы хотим создать:

Затем мы берём «Строителя»:

Пример использования:

Когда использовать: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.

Примеры на Java и Python.

Идея паттерна Стратегия (Strategy)

Паттерн проектирования — это продуманный способ построения исходного кода программы для решения часто возникающих в повседневном программировании проблем проектирования. Иными словами, это уже придуманное решения, для типичной задачи. При этом паттерн не готовое решение, а просто алгоритм действий, который должен привести к желаемому результату. Давайте рассмотрим один из наиболее часто используемых поведенческих паттернов — Стратегия (Strategy).

Как я уже писал ранее, существует три вида паттернов проектирования:

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

Стратегия (Strategy) — это поведенческий паттерн, который позволяет инкапсулировать группу взаимосвязанных алгоритмов, и при необходимости заменять их друг на друга, без привязки к конкретным пользователям. То есть, Стратегия позволяет скрыть часть логики, предоставив возможность ее изменения. 

Подпишись на  и Телеграм-канал. Там еще больше полезного контента для программистов.А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Шаблоны параллельного программирования

Используются для более эффективного написания многопоточных программ, и предоставляет готовые решения проблем синхронизации.Активный объект ( Active Object ) — Служит для отделения потока выполнения метода от потока, в котором он был вызван. Использует шаблоны асинхронный вызов методов и планировщик.Уклонитель ( Balking ) — Служит для выполнения действия над объектом только тогда, когда тот находится в корректном состоянии.Привязка свойств ( Binding properties ) — Комбинирует несколько наблюдателей для обеспечения синхронизации свойств в различных объектахОбмен сообщениями ( Messaging design pattern (MDP) ) — Позволяет компонентам и приложениям обмениваться информацией (сообщениями).Блокировка с двойной проверкой ( Double-checked locking ) — Предназначен для уменьшения накладных расходов, связанных с получением блокировки.Ассинхронные события ( Event-based asynchronous ) — Адресные проблемы с Асинхронным паттерном, которые возникают в программах с несколькими потоками.Охраняемая приостановка ( Guarded suspension ) — Используется для блокировки выполнения действия над объектом только тогда, когда тот находится в корректном состоянии.Полусинхронизация ( Half-Sync/Half-Async ) — пока нет данных про этот паттерн.Лидеры ( Leaders/followers ) — пока нет данных про этот паттерн.Замок ( Lock ) — Один поток блокирует ресурс для предотвращения доступа или изменения его другими потоками.Монитор ( Monitor object ) — Объект, предназначенный для безопасного использования более чем одним потоком.
Реактор ( Reactor ) — Предназначен для синхронной передачи запросов сервису от одного или нескольких источников.Блокировка чтение-запись ( Read write lock ) — Позволяет нескольким потокам одновременно считывать информацию из общего хранилища, но позволяя только одному потоку в текущий момент времени её изменять.Планировщик ( Scheduler ) — Обеспечивает механизм реализации политики планирования, но при этом не зависящих ни от одной конкретной политики.Пул потоков ( Thread pool ) — Предоставляет пул потоков для обработки заданий, представленных обычно в виде очереди.Спецпотоковое хранилище ( Thread-specific storage ) — Служит для предоставления различных глобальных переменных для разных потоков.Однопоточное выполнение ( Single thread execution ) — Препятствует конкурентному вызову метода, тем самым запрещая параллельное выполнение этого метода.Кооперативный паттерн ( Cooperative pattern ) — Обеспечивает механизм безопасной остановки потоков исполнения, используя общий флаг для сигнализирования прекращения работы потоков.

Псевдокод

В этом примере Посетитель добавляет в существующую иерархию классов геометрических фигур возможность экспорта в XML.

Пример организации экспорта объектов в XML через отдельный класс-посетитель.


// Сложная иерархия элементов.
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)

// Метод принятия посетителя должен быть реализован в каждом
// элементе, а не только в базовом классе. Это поможет программе
// определить, какой метод посетителя нужно вызвать, если вы не
// знаете тип элемента.
class Dot extends Shape is
// …
method accept(v: Visitor) is
v.visitDot(this)

class Circle extends Dot is
// …
method accept(v: Visitor) is
v.visitCircle(this)

class Rectangle extends Shape is
// …
method accept(v: Visitor) is
v.visitRectangle(this)

class CompoundShape implements Shape is
// …
method accept(v: Visitor) is
v.visitCompoundShape(this)

// Интерфейс посетителей должен содержать методы посещения
// каждого элемента

Важно, чтобы иерархия элементов менялась
// редко, так как при добавлении нового элемента придётся менять
// всех существующих посетителей.
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)

// Конкретный посетитель реализует одну операцию для всей
// иерархии элементов. Новая операция = новый посетитель.
// Посетитель выгодно применять, когда новые элементы
// добавляются очень редко, а новые операции — часто.
class XMLExportVisitor implements Visitor is
method visitDot(d: Dot) is
// Экспорт id и координат центра точки.

method visitCircle(c: Circle) is
// Экспорт id, кординат центра и радиуса окружности.

method visitRectangle(r: Rectangle) is
// Экспорт id, кординат левого-верхнего угла, ширины и
// высоты прямоугольника.

method visitCompoundShape(cs: CompoundShape) is
// Экспорт id составной фигуры, а также списка id
// подфигур, из которых она состоит.

// Приложение может применять посетителя к любому набору
// объектов элементов, даже не уточняя их типы

Нужный метод
// посетителя будет выбран благодаря проходу через метод accept.
class Application is
field allShapes: array of Shapes

method export() is
exportVisitor = new XMLExportVisitor()

foreach (shape in allShapes) do
shape.accept(exportVisitor)

Методы применения индикатора

Входить в рынок на основе данного алгоритма можно следующими способами:

  • Сразу же после возникновения стрелки, открываем ордер в ее направлении.
  • Вход в рынок осуществляем на откате после возникновения стрелки.
  • В момент пробоя экстремума, появившегося после возникновения стрелки.
  • В момент отбоя цены от границ канала в сторону возникшей до этого стрелки.

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

Обратите внимание на то, что если тейк размещать на первом уровне для выхода, тогда соотношение тейка к стопу будет не совсем выгодным. В этой связи лучше всего размещать тейк на втором уровне для выхода

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

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

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

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

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

Данный алгоритм можно применять для выявления глобальной тенденции, после чего применять пирамидинг в сторону тренда, выявленного на более старшем временном интервале. На следующей картинке вы можете увидеть сигналы индикатора на часовом графике.Индикатор 123PatternsV6 можно также использовать в качестве дополнения к вашей торговой стратегии. В качестве примера вспомним пробой трендовой прямой, который выступает в качестве сигнала смены тренда. Затем на основе индикатора 123PatternsV6 можно открывать и закрывать сделки.

Структура паттерна Flyweight

Клиенты не создают приспособленцев напрямую, а запрашивают их у фабрики. Любые атрибуты (члены данных класса), которые не могут разделяться, являются внешним состоянием. Внешнее состояние передается приспособленцу при вызове его методов. При этом наибольшая экономия памяти достигается в том случае, если внешнее состояние не хранится, а вычисляется при вызове.

UML-диаграмма классов паттерна Flyweight

Классы, описывающие различных насекомых Ant, Locust и Cockroach могут быть «легковесными», потому что специфичная для экземпляров информация может быть вынесена наружу и затем, передаваться клиентом в запросе.

Реализация паттерна Decorator

Паттерн Decorator: до и после

До

Используется следующая иерархия наследования:

class A {
  public:
    virtual void do_it() {
        cout 

Вывод программы:

AX
AXY
AXYZ

После

Заменим наследование делегированием.
Обсуждение. Используйте агрегирование вместо наследования для декорирования «основного» объекта. Тогда клиент сможет динамически добавлять новые обязанности объектам, в то время как архитектура, основанная на множественном наследовании, является статичной.

class I {
  public:
    virtual ~I(){}
    virtual void do_it() = 0;
};

class A: public I {
  public:
    ~A() {
        cout do_it();
    }
  private:
    I *m_wrappee;
};

class X: public D {
  public:
    X(I *core): D(core){}
    ~X() {
        cout do_it();
  cout do_it();
  cout do_it();
  cout 

Вывод программы:

AX
AXY
AXYZ
X dtor   A dtor
Y dtor   X dtor   A dtor
Z dtor   Y dtor   X dtor   A dtor

Паттерн проектирования Decorator по шагам

  • Создайте «наименьший общий знаменатель», делающий классы взаимозаменяемыми.
  • Создайте базовый класс второго уровня для реализации дополнительной функциональности.
  • Основной класс и класс-декоратор используют отношение «является».
  • Класс-декоратор «имеет» экземпляр «наименьшего общего знаменателя».
  • Класс делегирует выполнение операции объекту «имеет».
  • Для реализации каждой дополнительной функциональности создайте подклассы .
  • Подклассы делегируют выполнение операции базовому классу и реализуют дополнительную функциональность.
  • Клиент несет ответственность за конфигурирование нужной функциональности.
#include 
using namespace std;

// 1. " Наименьший общий знаменатель"
class Widget
{
  public:
    virtual void draw() = 0;
};

// 3. Основной класс, использующий отношение "является"
class TextField: public Widget
{
    int width, height;
  public:
    TextField(int w, int h)
    {
        width = w;
        height = h;
    }
 
    /*virtual*/
    void draw()
    {
        cout draw(); // 5. делегирование
    }
};

// 6. Дополнительное декорирование
class BorderDecorator: public Decorator
{
  public:
    BorderDecorator(Widget *w): Decorator(w){}
 
    /*virtual*/
    void draw()
    {
        // 7. Делегирование базовому классу и
        Decorator::draw();
        // 7. реализация дополнительной функциональности
        cout draw();
}
TextField: 80, 24
   ScrollDecorator
   BorderDecorator
   BorderDecorator

Обсуждение паттерна Decorator

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

UML-диаграмма классов паттерна Decorator

Эта схема имеет существенный недостаток — число классов сильно разрастается.

Паттерн Decorator дает клиенту возможность задавать любые комбинации желаемых «особенностей».

Widget*  aWidget = new BorderDecorator(
                        new HorizontalScrollBarDecorator(
                          new VerticalScrollBarDecorator(
                            new Window( 80, 24 ))));
aWidget->draw();

Гибкость может быть достигнута следующим дизайном.

Другой пример каскадного соединения свойств (в цепочку) для придания объекту нужных характеристик…

Stream*  aStream = new CompressingStream(
                        new ASCII7Stream(
                          new FileStream( "fileName.dat" )));
aStream->putString( "Hello world" );

Решение этого класса задач предполагает инкапсуляцию исходного объекта в абстрактный интерфейс. Как объекты-декораторы, так и основной объект наследуют от этого абстрактного интерфейса. Интерфейс использует рекурсивную композицию для добавления к основному объекту неограниченного количества «слоев» — декораторов.

Обратите внимание, паттерн Decorator позволяет добавлять объекту новые обязанности, не изменяя его интерфейс (новые методы не добавляются). Известный клиенту интерфейс должен оставаться постоянным на всех, следующих друг за другом «слоях»

Отметим также, что основной объект теперь «скрыт» внутри объекта-декоратора. Доступ к основному объекту теперь проблематичен.

Ссылка на основную публикацию