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

Декоратор добавляет объекту обязанности через обёртку с тем же интерфейсом. Обёртки можно комбинировать во время выполнения, не создавая подкласс для каждого сочетания функций.

Сигналы применения

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

Если задача только в переводе несовместимого интерфейса, см. Адаптер Adapter.

Роли и взаимодействие

  • Component — общий интерфейс;
  • Concrete Component — базовая реализация;
  • Base Decorator — хранит Component и делегирует ему;
  • Concrete Decorator — выполняет работу до или после делегирования;
  • Client — видит итоговую композицию как обычный Component.

Порядок обёрток является частью поведения: фильтр до и после округления может дать разные результаты.

Пример на Java

interface MetricSink { void write(String name, double value); }
final class ConsoleSink implements MetricSink {
    public void write(String name, double value) { System.out.println(name + "=" + value); }
}
abstract class SinkDecorator implements MetricSink {
    protected final MetricSink delegate;
    SinkDecorator(MetricSink delegate) { this.delegate = delegate; }
}
final class PrefixSink extends SinkDecorator {
    private final String prefix;
    PrefixSink(MetricSink delegate, String prefix) {
        super(delegate);
        this.prefix = prefix;
    }
    public void write(String name, double value) {
        delegate.write(prefix + name, value);
    }
}
final class NonNegativeSink extends SinkDecorator {
    NonNegativeSink(MetricSink delegate) { super(delegate); }
    public void write(String name, double value) {
        if (value >= 0) delegate.write(name, value);
    }
}
MetricSink sink = new NonNegativeSink(
        new PrefixSink(new ConsoleSink(), "runtime."));
sink.write("memory", 256);

Клиент собирает цепочку в composition root, а бизнес-код получает только MetricSink.

Компромиссы

  • обязанности комбинируются без множества подклассов;
  • каждый класс остаётся небольшим;
  • длинную цепочку трудно читать в отладчике;
  • порядок декораторов может быть неочевидным;
  • сравнение типов и снятие обёрток связывают клиента с реализацией;
  • если интерфейс возвращает this, нужно продумать, какой объект получит клиент.

Чем отличается

  • Заместитель Proxy управляет доступом, а Decorator расширяет поведение;
  • Адаптер Adapter предоставляет другой интерфейс;
  • Цепочка обязанностей Chain of Responsibility выбирает обработчик или пропускает запрос по цепи, тогда как декораторы образуют один составной объект;
  • Composite строит дерево, Decorator чаще образует линейную оболочку.

Вопросы на собеседовании

  1. Почему интерфейс тот же? Декоратор подставляется вместо компонента и вложится.
  2. Обязан ли декоратор делегировать? Нет, если фильтрация следует контракту.
  3. Как избежать сложной сборки? Вынести композицию в фабрику или DI-конфигурацию.
  4. Почему не наследование? Число комбинаций растёт быстрее числа функций.
  5. Потокобезопасность сохраняется? Нет, обёртку и делегат анализируют отдельно.

Связи: Структурные паттерны, Принципы SOLID, Шаблоны проектирования.