Menu
Coddy logo textTech

Паттерн Декоратор

Часть раздела Объектно-ориентированное программирование путешествия по 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

Декоратор идеально подходит, когда вам нужно добавить объектам новые обязанности без изменения их кода, особенно когда требуются различные комбинации функций. Он часто используется для слоев логирования, кэширования, аутентификации и сжатия.

challenge icon

Задание

Легко

Давайте создадим систему заказа в кофейне, используя паттерн Декоратор! Вы создадите базовый кофейный напиток и декораторы, которые добавляют добавки, такие как молоко, сахар и взбитые сливки. Каждый декоратор оборачивает предыдущий, наращивая как описание, так и цену — отличный реальный пример того, как декораторы накладывают функциональность слоями.

Вы организуете свой код в трех файлах:

  • 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 с ровно двумя знаками после запятой
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование