Menu
Coddy logo textTech

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: 330

When 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 *** plan

Decorators 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:

370
challenge icon

Challenge

Easy

Pizzas 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(", "))
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Kotlin playground