Что это такое
Паттерн Decorator добавляет объекту поведение, оборачивая его в другой объект с тем же интерфейсом. Каждая обёртка делает свою маленькую добавку и передаёт вызов дальше — тому, что она оборачивает.
Поскольку обёртка и обёрнутый объект имеют один интерфейс, обёртки складываются в любых сочетаниях — как слои одежды.
Примечание: на этой странице мы оборачиваем с помощью классов, потому что слои хранят состояние (объект, который они оборачивают) и вложенность хорошо видна. Обёрткой может быть и функция, которая принимает функцию и возвращает новую, — это тот же паттерн в меньшем масштабе.
Пример: оборачиваем уведомитель
Простой уведомитель отправляет письмо. Одна обёртка добавляет запись в журнал перед отправкой, другая добавляет смс после неё. Для вызывающего кода это по-прежнему обычный Notifier.
Слои вокруг ядра
Запись new WithLog(new WithSMS(new EmailNotifier())) читается изнутри наружу: почта в ядре, смс вокруг неё, журнал — самый внешний слой. Вывод показывает именно этот порядок: строка журнала, письмо, затем смс. Любой слой можно добавить, убрать или переставить, не трогая остальные.
Вывод: программа на TypeScript и захваченный вывод
interface Notifier {
send(msg: string): void
}
class EmailNotifier implements Notifier {
send(msg: string): void {
console.log("почта:", msg)
}
}
// Каждый декоратор оборачивает Notifier и добавляет один слой.
class WithLog implements Notifier {
private wrapped: Notifier
constructor(wrapped: Notifier) {
this.wrapped = wrapped
}
send(msg: string): void {
console.log("журнал: отправляем уведомление")
this.wrapped.send(msg)
}
}
class WithSMS implements Notifier {
private wrapped: Notifier
constructor(wrapped: Notifier) {
this.wrapped = wrapped
}
send(msg: string): void {
this.wrapped.send(msg)
console.log("смс:", msg)
}
}
const notifier = new WithLog(new WithSMS(new EmailNotifier()))
notifier.send("сервер не отвечает")
Когда его использовать
Используйте его, чтобы добавить необязательное и сочетаемое поведение — журналирование, кеширование, повторные попытки, метрики — вокруг ядра, которое вы не хотите менять. Middleware в веб-серверах — это в точности данный паттерн.
Откажитесь от него, когда поведение не является необязательным и не сочетается с другими: тогда его место внутри самого класса, где его проще найти.
Плюсы и минусы
Плюсы: поведение добавляется без изменения основного класса; слои свободно сочетаются; каждый слой мал и тестируется отдельно.
Минусы: глубокую стопку обёрток трудно отлаживать — вызов проходит долгий путь, прежде чем что-то сделать; строки создания объектов становятся длинными.