Что это такое
Паттерн Decorator добавляет объекту поведение, оборачивая его в другой объект с тем же интерфейсом. Каждая обёртка делает свою небольшую работу и передаёт вызов дальше — тому, кого она оборачивает.
Поскольку обёртка и обёрнутый объект имеют общий интерфейс, обёртки складываются в любых сочетаниях — как слои одежды.
Пример: оборачиваем уведомитель
Простой уведомитель отправляет письмо. Одна обёртка добавляет запись в лог перед отправкой, другая — отправку SMS после неё. Вызывающий код по-прежнему видит обычный Notifier.
Слои вокруг ядра
WithLog{WithSMS{EmailNotifier{}}} читается изнутри наружу: письмо в ядре, SMS вокруг него, лог — самый внешний слой. Любой слой можно добавить, убрать или переставить, не трогая остальные.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
type Notifier interface {
Send(msg string)
}
type EmailNotifier struct{}
func (EmailNotifier) Send(msg string) {
fmt.Println("email:", msg)
}
// Каждый декоратор оборачивает Notifier и добавляет один слой.
type WithLog struct {
wrapped Notifier
}
func (w WithLog) Send(msg string) {
fmt.Println("лог: отправляем уведомление")
w.wrapped.Send(msg)
}
type WithSMS struct {
wrapped Notifier
}
func (w WithSMS) Send(msg string) {
w.wrapped.Send(msg)
fmt.Println("sms:", msg)
}
func main() {
n := WithLog{WithSMS{EmailNotifier{}}}
n.Send("сервер недоступен")
}
Когда его использовать
Используйте, чтобы добавить необязательное и комбинируемое поведение — логирование, кеширование, повторные попытки, метрики — вокруг ядра, которое вы не хотите менять. Middleware в веб-серверах — это ровно этот паттерн.
Не используйте, когда поведение не является необязательным и не комбинируется: тогда его место внутри самого типа, где его проще найти.
Плюсы и минусы
Плюсы: поведение добавляется без изменения основного типа; слои свободно комбинируются; каждый слой мал и тестируется отдельно.
Минусы: глубокую стопку обёрток трудно отлаживать — вызов проходит длинный путь, прежде чем что-то сделает; строки создания объектов становятся длинными.