Декоратор (Decorator, Wrapper)

Метафора

 

Как понятно из названия, данный паттерн чаще всего используется для расширения исходного объекта до требуемого вида. Например, мы условно можем считать «декоратором» человека с кистью и красной краской. Таким образом, какой бы объект (или определенный тип объектов) мы не передали в руки «декоратору», на выходе мы будем получать красные объекты.

 

Назначение

 

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

Расширение обычно подразумевает добавление кода перед, после или вместо основной функциональности методов объекта.

Хорошо спроектированный класс отвечает строго за определенные обязанности (high cohesion), декоратор позволяет легко наложить на него второстепенные задачи. Типичными второстепенными задачами могут быть: логирование, проверка полномочий, замер производительности, кеширование и т.п.

Недостатки паттерна:

  • Чувствительность к порядку инициализации декораторов,
  • Увеличение общей сложности и сложности при отладке.

 

Диаграмма

 

Декоратор

Декоратор

Базовый декоратор наследует тот же интерфейс что и компонент, благодаря чему клиент использующий декоратор может с ним работать как с компонентом. Конкретные декораторы добавляют дополнительное состояние (ConcreteDecoratorA) и дополнительное поведение (ConcreteDecoratorB), внутри переопределенных методов они вызывают методы внедрённого в них компонента (обычно внедрение происходит через конструктор, см. пример).

 

Пример

 

В примере есть основной компонент lcl_concrete_component, который отвечает за чтение данных из БД, на этом его обязанности заканчиваются. Предположим, что нам понадобилось навесить на него обязанность вести журнал и проверку авторизации. Одним из решений является наследование от lcl_concrete_component и определение доп. логики в новом классе. Но что делать, если навешиваемое поведение необходимо определять динамически? Можно добавить параметры ведения логов и авторизации по параметру, но это не наш вариант, воспользуемся шаблоном декоратор.

Мы определяем базовый абстрактный класс декоратора, который является наследником компонента и содержит внутри себя ссылку на конкретный компонент. Далее создаём конкретных декораторов, которые внутри себя уже реализуют необходимое поведение: ведение логов и проверку полномочий.

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

  • Игорь

    ASTRAFOX, здравствуйте. Спасибо за интересный блог.
    У меня возник вопрос по данной статье.
    Возможно я чего-то не понимаю, но в вашей реализации класс декоратор наследуется напрямую от lcl_concrete_component, что не соответствует схеме.
    При такой ситуации невозможно использование декоратора логирования и авторизации, например, для lcl_concrete_component1, lcl_concrete_component2, которые являются наследником lcl_component .

    • Действительно, наследовать нужно от lcl_component (как в схеме). Спасибо за исправление!

  • «Типичными второстепенными задачами могут быть: логирование, проверка полномочий, замер производительности, кеширование и т.п.» — эти задачи относятся, скорее, к реализации прокси, так как данные не изменяются по результатам выполнения метода-обертки.
    У тебя в описании к прокси даже есть эта фраза: «Однако у них [прокси и декоратор] разные цели, заместитель в отличие от декоратора не должен изменять результаты операции реального объекта».
    Как вариант примера: фильтрация данных, полученных из основного объекта, в зависимости от каких-либо хардкодных параметров или проверки полномочий (можно выдумать еще что-нибудь =D )