Паттерн декоратор
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 47 из 57.
Паттерн декоратор добавляет объекту поведение, оборачивая его в другой объект с тем же интерфейсом. Декоратор делегирует вызовы обёрнутому объекту и добавляет свою собственную часть, поэтому декораторы можно объединять в цепочку:
interface Coffee {
fun cost(): Int
fun description(): String
}
class Espresso : Coffee {
override fun cost() = 200
override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 50
override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 30
override fun description() = inner.description() + ", syrup"
}Внутри main:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")Вывод:
espresso, milk, milk, syrup: 330Когда декораторы изменяют результат, их порядок имеет значение: каждый из них работает с тем, что вернул объект внутри него:
interface Text {
fun render(): String
}
class Word(private val w: String) : Text {
override fun render() = w
}
class Bracket(private val inner: Text) : Text {
override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
override fun render() = inner.render() + inner.render()
}Внутри main:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())Вывод:
[hi][hi]
[hihi]С помощью by декоратор записывает только изменяемые им элементы, а остальные автоматически перенаправляет:
interface Source {
fun read(): String
fun name(): String
}
class FileSource(private val content: String) : Source {
override fun read() = content
override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
override fun read() = inner.read().replace(word, "***")
}Внутри main:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")Результат:
notes.txt: the *** planДекораторы объединяются во время выполнения программы, например на основе выбора клиента, тогда как подкласс фиксирует одну комбинацию при написании кода. Для трёх дополнительных компонентов в виде подклассов потребовался бы класс для каждой комбинации; в виде декораторов fold выстраивает их в цепочку из списка:
interface Coffee {
fun cost(): Int
}
class Espresso : Coffee {
override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
override fun cost() = inner.cost() + price
}Внутри main:
val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())Результат:
370Задание
ЛегкоПиццы создаются с помощью декораторов предоставленного интерфейса Pizza (с методами price() и toppings()). Напишите Base (800, без начинок) и декораторы Cheese (+150, добавляет cheese), Olives (+100, добавляет olives) и DoubleUp, который удваивает цену всего, что находится внутри него, и дважды повторяет его начинки. Затем напишите build(names), которая начинает с Base и оборачивает его одним декоратором для каждого имени по порядку (cheese, olives или double).
Предоставленный код считывает строки с разделёнными запятыми именами или plain, создаёт каждую пиццу и выводит 1050: cheese, olives или 800: plain.
Ваш код должен находиться в файлах Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt и Pizza.kt. В Main.kt находится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
for (line in input) {
val names = if (line == "plain") listOf() else line.split(",")
val pizza = build(names)
val toppings = pizza.toppings()
println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin