Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 93 из 107.
Паттерн Декоратор позволяет динамически добавлять объектам новое поведение, оборачивая их в другие объекты. В то время как Адаптер изменяет интерфейс для соответствия другому, Декоратор сохраняет тот же интерфейс, но расширяет функциональность, накладывая слои оберток вокруг исходного объекта.
В Go мы реализуем это, заставляя как базовый тип, так и декораторы реализовывать один и тот же интерфейс:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Декораторы оборачивают другой Notifier и добавляют поведение до или после делегирования ему:
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}Сила Декоратора заключается в наслоении нескольких оберток для объединения поведений:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLOДекоратор идеально подходит, когда вам нужно добавить объектам новые обязанности без изменения их кода, особенно когда требуются различные комбинации функций. Он часто используется для слоев логирования, кэширования, аутентификации и сжатия.
Задание
ЛегкоДавайте создадим систему заказа в кофейне, используя паттерн Декоратор! Вы создадите базовый кофейный напиток и декораторы, которые добавляют добавки, такие как молоко, сахар и взбитые сливки. Каждый декоратор оборачивает предыдущий, наращивая как описание, так и цену — отличный реальный пример того, как декораторы накладывают функциональность слоями.
Вы организуете свой код в трех файлах:
beverage.go: Определите основной интерфейс и базовый тип кофе.Создайте интерфейс
Beverageс двумя методами:Description() string— возвращает описание напиткаCost() float64— возвращает цену
Реализуйте структуру
Coffeeв качестве базового напитка. Ее описание должно бытьCoffee, а стоимость —2.00.decorators.go: Создайте типы декораторов, которые добавляют добавки к любому напитку.Каждый декоратор оборачивает
Beverageи реализует тот же интерфейс. Создайте три декоратора:MilkDecorator— добавляет, Milkк описанию и0.50к стоимостиSugarDecorator— добавляет, Sugarк описанию и0.25к стоимостиWhipDecorator— добавляет, Whipк описанию и0.75к стоимости
Каждый декоратор должен вызывать методы обернутого напитка и дополнять результат.
main.go: Создавайте индивидуальные напитки, накладывая декораторы друг на друга.Считайте количество добавок. Затем для каждой добавки считайте ее тип (
milk,sugarилиwhip) и оберните текущий напиток соответствующим декоратором.После применения всех добавок выведите итоговое описание напитка и его стоимость на отдельных строках. Отформатируйте стоимость как
$X.XXровно с двумя знаками после запятой.
Будут предоставлены следующие входные данные:
- Строка 1: Количество добавляемых добавок
- Последующие строки: По одному типу добавки на строку (
milk,sugarилиwhip)
Например, для входных данных:
2
milk
sugarВаш вывод должен быть:
Coffee, Milk, Sugar
$2.75И для входных данных:
3
whip
milk
milkВаш вывод должен быть:
Coffee, Whip, Milk, Milk
$3.75И для входных данных:
0Ваш вывод должен быть:
Coffee
$2.00Обратите внимание, как каждый декоратор оборачивает предыдущий напиток, наращивая описание и стоимость. Вы можете добавлять одну и ту же добавку несколько раз (например, двойное молоко), а порядок декораторов определяет порядок в описании. Базовый кофе ничего не знает о добавках — каждый декоратор просто дополняет то, что он оборачивает!
Шпаргалка
Паттерн Декоратор динамически добавляет объектам новое поведение, оборачивая их в другие объекты. Он сохраняет тот же интерфейс, но расширяет функциональность за счет многослойности.
И базовый тип, и декораторы реализуют один и тот же интерфейс:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Декораторы оборачивают другой объект и добавляют поведение до или после делегирования:
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}Складывайте несколько декораторов в стек, чтобы комбинировать поведение:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLOИспользуйте Декоратор для добавления обязанностей без изменения кода объекта, особенно для различных комбинаций функций. Распространенные варианты использования: логирование, кэширование, аутентификация, сжатие.
Попробуйте сами
package main
import (
"fmt"
)
func main() {
// Считать количество добавок
var count int
fmt.Scanln(&count)
// Начать с базового кофе
var drink Beverage = &Coffee{}
// Прочитать каждую добавку и обернуть напиток соответствующим декоратором
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: В зависимости от типа добавки, оберните напиток соответствующим декоратором
// Используйте MilkDecorator для "milk"
// Используйте SugarDecorator для "sugar"
// Используйте WhipDecorator для "whip"
}
// TODO: Вывести итоговое описание и стоимость
// Форматировать стоимость как $X.XX с ровно двумя знаками после запятой
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool