Паттерн декоратор
Часть раздела Объектно-ориентированное программирование путешествия по Swift на Coddy. Урок 47 из 57.
Паттерн декоратор добавляет поведение объекту, оборачивая его в другой объект с тем же протоколом. Обёртка перенаправляет вызов обёрнутому объекту и добавляет свою часть до или после него:
protocol Coffee {
func cost() -> Int
func label() -> String
}
struct Espresso: Coffee {
func cost() -> Int { 200 }
func label() -> String { "espresso" }
}
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())Вывод:
espresso + milk
250Поскольку декоратор сам является Coffee, декораторы могут оборачивать друг друга в любой комбинации и порядке, без отдельного подкласса для каждой комбинации:
protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 30 }
func label() -> String { base.label() + " + syrup" }
}
let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())Результат:
espresso + milk + milk + syrup
330Декораторы подходят для добавления сквозной функциональности, такой как ведение журнала, кэширование или проверка, вокруг существующего компонента, который остаётся неизменным:
protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }
struct Logged: DataSource {
let inner: any DataSource
func fetch(_ id: Int) -> String {
print("fetching \(id)")
return inner.fetch(id)
}
}
let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))Вывод:
fetching 7
row 7Код, использующий объект, видит только протокол, поэтому не может отличить декорированный объект от обычного. Именно это делает шаблон безопасным для последующего добавления.
func printReceipt(_ c: any Coffee) { // работает для Espresso и для любой обёрнутой версии
print("\(c.label()): \(c.cost())")
}Задание
ЛегкоПиццы создаются с помощью декораторов. Протокол Pizza содержит price() и toppings() (массив названий). Base имеет цену 800 и не содержит начинок. Реализуйте декораторы Cheese (+150), Olives (+100) и Double (удваивает цену всего, что находится внутри него, и повторяет его начинки дважды). Завершите реализацию build(_:): она начинает с Base и оборачивает его одним декоратором для каждого названия по порядку.
Предоставленный код считывает строки с разделёнными запятыми названиями начинок (или пустую строку), создаёт каждую пиццу и выводит 1050: cheese, olives или 800: plain.
Ваш код должен находиться в файлах Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift и Functions.swift. В файле main.swift находится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
// Предоставленный код ввода/вывода: оставьте его как есть
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
let p = build(names)
let tops = p.toppings()
print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовМетоды и selfИнициализаторыКлассы — это ссылкиПовторение — читательский билет4Инкапсуляция
Контроль доступаТолько чтение извнеЗащита состоянияИнициализаторы с возможностью сбояПовторение — банковский счёт7Полиморфизм и обобщения
ПолиморфизмПриведение типовОбобщённые функцииОбобщённые типыПовторение — калькулятор фигур10Паттерны проектирования. Часть 2
Паттерн делегированияПаттерн декораторПаттерн строительПаттерн состояния2Структуры и типы-значения
СтруктурыСемантика значенийИзменяющие методыСтруктура или классПовторение — элемент инвентаря5Наследование
ПодклассыПереопределение методовИнициализаторы и superfinal и иерархииПовторение — иерархия сотрудников8Стандартные протоколы
CustomStringConvertibleEquatableComparableHashableПовторение — пользовательская сортировка11Проект: Управление библиотекой
Книги и читателиВыдача книгПотренируйтесь самостоятельно: Песочница Swift