Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 93 из 107.
Паттерн Decorator позволяет динамически добавлять новое поведение объектам, оборачивая их в другие объекты. В то время как Adapter изменяет интерфейс, чтобы он соответствовал другому, Decorator сохраняет тот же интерфейс, но расширяет функциональность, накладывая оболочки поверх исходного объекта.
В Go мы реализуем это, заставляя и базовый тип, и декораторы реализовывать один и тот же interface:
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))
}Сила Decorator заключается в последовательном добавлении нескольких обёрток для объединения поведений:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Отправка: Hello
fmt.Println(withBoth.Send("Hello"))
// ОТПРАВКА: [2024-01-15] HELLOДекоратор идеально подходит, когда нужно добавить объектам обязанности, не изменяя их код, особенно если требуются различные комбинации функций. Он часто используется для журналирования, кэширования, аутентификации и слоёв сжатия.
Задание
ЛегкоДавайте создадим систему заказа в кофейне с использованием паттерна Decorator! Вы создадите базовый кофейный напиток и декораторы, которые добавляют такие extras, как молоко, сахар и взбитые сливки. Каждый декоратор оборачивает предыдущий, формируя и описание, и цену: прекрасный пример из реального мира того, как декораторы послойно добавляют функциональность.
Вы организуете код в трёх файлах:
beverage.go: Define основной interface и базовый тип кофе.Создайте interface
Beverageс двумя methods:Description() string: возвращает информацию о том, что представляет собой напитокCost() float64: возвращает цену
Implement структуру
Coffeeкак базовый beverage. Его описание должно бытьCoffee, а его cost —2.00.decorators.go: Создайте типы декораторов, которые добавляют extras к любому beverage.Каждый декоратор оборачивает
Beverageи реализует тот же interface. Создайте три декоратора:MilkDecorator: добавляет, Milkк описанию и0.50к costSugarDecorator: добавляет, Sugarк описанию и0.25к costWhipDecorator: добавляет, Whipк описанию и0.75к cost
Каждый декоратор должен вызывать methods обёрнутого beverage и дополнять результат.
main.go: Создавайте напитки с индивидуальными настройками, объединяя декораторы.Считайте количество добавляемых extras. Затем для каждого extra считайте его тип (
milk,sugarилиwhip) и оберните текущий beverage соответствующим декоратором.После применения всех extras выведите описание и cost итогового напитка в отдельных строках. Отформатируйте cost как
$X.XX, ровно с двумя знаками после десятичной точки.
Будут предоставлены следующие входные данные:
- Строка 1: количество добавляемых extras
- Следующие строки: по одному типу extra в каждой строке (
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Обратите внимание, как каждый декоратор оборачивает предыдущий beverage, формируя и описание, и cost. Вы можете добавлять один и тот же extra несколько раз (например, двойное молоко), а порядок декораторов определяет порядок в описании. Базовый кофе ничего не знает об extras: каждый декоратор просто дополняет то, что он оборачивает!
Попробуйте сами
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: В зависимости от типа добавки обернуть drink соответствующим декоратором
// Использовать 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Потренируйтесь самостоятельно: Онлайн-компилятор Go