Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 47 von 57.

Das Dekorator-Muster fügt einem Objekt Verhalten hinzu, indem es in ein anderes Objekt mit derselben Schnittstelle eingebettet wird. Der Dekorator leitet Aufrufe an das Objekt weiter, das er einbettet, und fügt seinen eigenen Teil hinzu, sodass Dekoratoren gestapelt werden können:

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"
}

Innerhalb von main:

val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")

Ausgabe:

espresso, milk, milk, syrup: 330

Wenn Dekoratoren das Ergebnis verändern, ist ihre Reihenfolge wichtig: Jeder arbeitet mit dem, was das Objekt in ihm zurückgegeben hat:

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()
}

In main:

println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())

Ausgabe:

[hi][hi]
[hihi]

Mit by schreibt ein Dekorator nur die Member, die er ändert, und leitet den Rest automatisch weiter:

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, "***")
}

Innerhalb von main:

val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")

Ausgabe:

notes.txt: the *** plan

Dekoratoren werden während der Programmausführung kombiniert, zum Beispiel anhand der Auswahl eines Kunden, während eine Unterklasse eine Kombination bereits beim Schreiben des Codes festlegt. Drei Extras als Unterklassen würden eine Klasse für jede Kombination erfordern; als Dekoratoren stapelt fold sie aus einer Liste:

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
}

In 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())

Ausgabe:

370
challenge icon

Aufgabe

Einfach

Pizzas werden mit Dekoratoren der bereitgestellten Schnittstelle Pizza (mit price() und toppings()) erstellt. Schreibe Base (800, ohne Belag) sowie die Dekoratoren Cheese (+150, fügt cheese hinzu), Olives (+100, fügt olives hinzu) und DoubleUp, der den Preis von allem darin verdoppelt und den Belag zweimal wiederholt. Schreibe anschließend build(names), das mit Base beginnt und es für jeden Namen der Reihe nach mit einem Dekorator umhüllt (cheese, olives oder double).

Der bereitgestellte Code liest Zeilen mit durch Kommas getrennten Namen oder plain, erstellt jede Pizza und gibt 1050: cheese, olives oder 800: plain aus.

Dein Code gehört in Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt und Pizza.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

fun main() {
    // Gelieferter Ein-/Ausgabe-Code: belasse ihn wie er ist
    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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Kotlin-Playground