Декоратор (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 чаще образует линейную оболочку.
Вопросы на собеседовании
- Почему интерфейс тот же? Декоратор подставляется вместо компонента и вложится.
- Обязан ли декоратор делегировать? Нет, если фильтрация следует контракту.
- Как избежать сложной сборки? Вынести композицию в фабрику или DI-конфигурацию.
- Почему не наследование? Число комбинаций растёт быстрее числа функций.
- Потокобезопасность сохраняется? Нет, обёртку и делегат анализируют отдельно.
Связи: Структурные паттерны, Принципы SOLID, Шаблоны проектирования.