Decorator (Декоратор)¶
Категория: структурный паттерн.
Проблема¶
Нужно добавить объекту дополнительное поведение (логирование, кеширование, подсчёт стоимости, шифрование), но:
- через наследование это привело бы к комбинаторному взрыву подклассов на каждое сочетание "фич" (EspressoWithMilk, EspressoWithSyrup, EspressoWithMilkAndSyrup, ...);
- поведение нужно добавлять не всем экземплярам класса, а только конкретным, и желательно динамически, в рантайме, а не жёстко на этапе компиляции через выбор подкласса.
Решение¶
- Выделяется общий интерфейс для базового объекта и всех "надстроек" над ним.
- Каждая дополнительная возможность оформляется отдельным классом-декоратором, который:
- реализует тот же интерфейс, что и оборачиваемый объект;
- хранит ссылку на этот объект;
- в своих методах вызывает методы обёрнутого объекта и добавляет к результату своё поведение.
- Декораторы можно комбинировать, оборачивая один в другой в произвольном порядке - каждый уровень обёртки ничего не знает о других уровнях.
Структура¶
Component- общий интерфейс базового объекта и декораторов.ConcreteComponent- базовая реализация, которую оборачивают.Decorator- абстрактный класс/интерфейс, хранящий ссылку наComponentи делегирующий ему вызовы по умолчанию.ConcreteDecorator- добавляет конкретное поведение до/после делегирования вызова обёрнутому объекту.
Когда применять¶
- Нужно добавлять и убирать обязанности объекта динамически, в рантайме, без изменения его класса.
- Расширение через наследование невозможно (класс
sealed) или привело бы к взрывному росту числа подклассов на все комбинации возможностей. - Несколько независимых "сквозных" улучшений (логирование, кеш, ретраи, метрики) нужно применять к разным объектам в разных сочетаниях.
Плюсы¶
- Позволяет комбинировать поведения без множественного наследования и без разрастания иерархии классов.
- Соблюдает принцип единственной обязанности: каждый декоратор отвечает ровно за одну добавленную возможность.
- Можно добавлять/убирать декораторы в рантайме, просто меняя то, во что "завёрнут" объект.
Минусы¶
- Много маленьких, похожих друг на друга объектов - это усложняет отладку (нужно "размотать" цепочку оберток, чтобы понять итоговое поведение).
- Порядок применения декораторов иногда имеет значение и может быть неочевиден при чтении кода.
- Декоратор и оборачиваемый объект должны быть максимально похожи по интерфейсу - если декоратору нужно добавить новый метод, которого нет в базовом интерфейсе, паттерн перестаёт работать так гладко.
Отличие от Proxy¶
Оба паттерна оборачивают объект за тем же интерфейсом, но по назначению они разные: Decorator добавляет новое поведение (расширяет функциональность), Proxy контролирует доступ к объекту (может вообще не выполнять реальный вызов - например, при кешировании или проверке прав). Технически код может выглядеть похоже, разница - в цели использования.
Пример в .NET Framework / BCL¶
- Иерархия
Stream:GZipStream,CryptoStream,BufferedStreamоборачивают базовыйStream(например,FileStream), добавляя сжатие, шифрование или буферизацию - классический учебный пример Decorator прямо из BCL. - Middleware в ASP.NET Core частично сочетает в себе идеи Decorator и Chain of Responsibility - каждый middleware оборачивает следующий в цепочке и может добавить поведение до/после вызова
next().