Decorator Pattern
Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 47 of 57.
The decorator pattern adds behaviour to an object by wrapping it in another object with the same interface. The decorator forwards to the object it wraps and adds its own part, so decorators can be stacked:
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"
}Inside main:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")Output:
espresso, milk, milk, syrup: 330When decorators change the result, their order matters: each one works on what the object inside it returned:
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()
}Inside main:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())Output:
[hi][hi]
[hihi]With by, a decorator writes only the members it changes and forwards the rest automatically:
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, "***")
}Inside main:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")Output:
notes.txt: the *** planDecorators are combined while the program runs, for example from a customer's choices, while a subclass fixes one combination when the code is written. Three extras as subclasses would need a class for every combination; as decorators, fold stacks them from a list:
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
}Inside 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())Output:
370Challenge
EasyPizzas are built with decorators of the supplied interface Pizza (with price() and toppings()). Write Base (800, no toppings) and the decorators Cheese (+150, adds cheese), Olives (+100, adds olives) and DoubleUp, which doubles the price of everything inside it and repeats its toppings twice. Then write build(names), which starts from Base and wraps it with one decorator per name, in order (cheese, olives or double).
The supplied code reads lines of comma-separated names, or plain, builds each pizza and prints 1050: cheese, olives or 800: plain.
Your code goes in Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt and Pizza.kt. Main.kt holds the supplied input/output code and cannot be edited.
Try it yourself
fun main() {
// Supplied input/output code: keep it as it is
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(", "))
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesPrimary Constructorsinit Blocks and ValidationSecondary ConstructorsRecap - Library Card4Inheritance
Open Classes and SubclassesOverriding Memberssuper and ConstructorsAbstract ClassesRecap - Staff Directory2Properties and Encapsulation
Custom AccessorsVisibility ModifiersPrivate Setterslazy and lateinitRecap - Bank Account5Interfaces
Declaring InterfacesDefault MethodsMultiple InterfacesInterfaces as TypesRecap - Payment Methods8Generics
Generic FunctionsGeneric ClassesType ConstraintsVariance With in and outRecap - Priority Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Kotlin playground