Decorator

Паттерн, который наращивает поведение объекта слоями-обёртками — без взрыва числа подклассов.

Decorator динамически добавляет объекту новые обязанности, оборачивая его в объект с тем же интерфейсом.

Какую задачу решает

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

Идея и реализация

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

from abc import ABC, abstractmethod


class Coffee(ABC):
    @abstractmethod
    def cost(self): ...
    @abstractmethod
    def desc(self): ...


class Espresso(Coffee):
    def cost(self): return 100
    def desc(self): return "эспрессо"


class Addon(Coffee):             # базовый декоратор
    def __init__(self, wrapped: Coffee):
        self._wrapped = wrapped


class Milk(Addon):
    def cost(self): return self._wrapped.cost() + 30
    def desc(self): return self._wrapped.desc() + " + молоко"


class Syrup(Addon):
    def cost(self): return self._wrapped.cost() + 20
    def desc(self): return self._wrapped.desc() + " + сироп"


order = Syrup(Milk(Espresso()))   # оборачиваем слоями
print(order.desc(), "=", order.cost())

Вывод:

эспрессо + молоко + сироп = 150

Каждый слой — самостоятельный объект. Syrup(Milk(Espresso())) читается как «эспрессо, потом молоко, потом сироп». Комбинации собираются в рантайме, новые добавки — это новые классы-декораторы, а не комбинаторный взрыв подклассов.

Чем отличается от наследования

НаследованиеDecorator
Фиксируется на этапе компиляцииСобирается в рантайме
N добавок → до 2^N классовN добавок → N классов
Одна цепочка наследованияЛюбые комбинации обёрток

Decorator против @декораторов Python

Не путайте паттерн с синтаксисом @ в Python. Питоновские декораторы функций — родственная идея (обёртка), но это другой механизм. Паттерн Decorator из GoF — это про объекты и интерфейсы, как в примере выше.

Где встречается

Потоки ввода-вывода (буферизация поверх файла поверх сжатия), middleware в веб-фреймворках, обёртки прав доступа и логирования, UI-компоненты со скроллом/рамкой.

Итог

  • Decorator добавляет поведение, оборачивая объект в объект того же интерфейса.
  • Избавляет от комбинаторного взрыва подклассов.
  • Комбинации задаются в рантайме — гибче наследования.
Проверьте себя
1. Главное преимущество Decorator перед наследованием?
AОн быстрее работает
BКомбинации поведения собираются в рантайме, без взрыва числа подклассов
CОн не требует интерфейсов
DОн экономит память
2. Что обязательно для класса-декоратора?
AБыть синглтоном
BРеализовывать тот же интерфейс, что и оборачиваемый объект, и хранить ссылку на него
CНаследоваться от object
DИметь метод clone
3. Питоновский синтаксис @decorator и паттерн Decorator — это…
AОдно и то же
BРодственные по идее, но разные механизмы
CПолностью несвязанные вещи
DОба про создание объектов
Поддержать проект