Menu
Coddy logo textTech

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

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

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

challenge icon

Задание

Легко

Давайте создадим систему заказа в кофейне с использованием паттерна 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 к cost
    • SugarDecorator: добавляет , Sugar к описанию и 0.25 к cost
    • WhipDecorator: добавляет , 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 ровно с двумя знаками после запятой
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go