Для чего нужен шаблон декоратор
Что проверяют
Умеете ли вы перенести паттерн из мира классов в Go, где нет наследования, и узнаёте ли его в стандартной библиотеке. Следом обычно спрашивают, чем декоратор отличается от прокси и адаптера и в каком порядке срабатывают обёртки.
Как отвечать
Декоратор добавляет поведение объекту, не меняя ни его код, ни его интерфейс. В Go это обёртка: тип реализует тот же интерфейс и держит внутри исходную реализацию.
type loggingNotifier struct{ next Notifier }
func (l loggingNotifier) Send(msg string) error {
log.Printf("send: %s", msg)
return l.next.Send(msg)
}Примеры из стандартной библиотеки убеждают сильнее выдуманных:
io.Reader:bufio.NewReader,gzip.NewReader,io.LimitReader,io.TeeReaderоборачивают другойReaderи добавляют буфер, распаковку, лимит, копию;- HTTP middleware: функция
func(http.Handler) http.Handlerдобавляет логирование, метрики, авторизацию, восстановление после паники; http.RoundTripper: обёртка вокруг транспорта клиента для ретраев, трейсинга и подписи запросов.
Обёртки собираются в цепочку, и порядок важен: внешняя срабатывает первой на входе и последней на выходе. Поэтому recovery ставят снаружи, а логирование так, чтобы оно видело итоговый статус ответа.
Отличия от соседей: адаптер меняет интерфейс, прокси управляет доступом к объекту, декоратор сохраняет интерфейс и добавляет поведение.
Где спотыкаются
Копируют Java-схему с абстрактным базовым декоратором и лишним уровнем структур. В Go хватает одного поля с интерфейсом или замыкания.
Теряют необязательные интерфейсы. Обёртка над http.ResponseWriter прячет http.Flusher и http.Hijacker, и стриминг перестаёт работать. Помогает метод Unwrap(), который понимает http.ResponseController, или явная реализация нужных интерфейсов.
Встраивают интерфейс в структуру, чтобы переопределить один метод, и забывают заполнить поле. Вызов непереопределённого метода на nil даёт панику уже в рантайме, компилятор не предупредит.