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: 330Wenn 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 *** planDekoratoren 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:
370Aufgabe
EinfachPizzas 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(", "))
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenPrimärkonstruktoreninit-Blöcke und ValidierungSekundärkonstruktorenRückblick – Bibliotheksausweis4Vererbung
Offene Klassen und UnterklassenÜberschreiben von Membernsuper und KonstruktorenAbstrakte KlassenRückblick – Mitarbeiterverzeichnis2Eigenschaften und Kapselung
Benutzerdefinierte ZugriffsmethodenSichtbarkeitsmodifikatorenPrivate Setterlazy und lateinitWiederholung – Bankkonto5Schnittstellen
Schnittstellen deklarierenStandardmethodenMehrere SchnittstellenSchnittstellen als TypenRückblick – Zahlungsmethoden8Generics
Generische FunktionenGenerische KlassenTypbeschränkungenVarianz mit in und outRückblick – Prioritätswarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Kotlin-Playground