Wzorzec dekoratora
Część sekcji Programowanie obiektowe ścieżki Kotlin w Coddy. Lekcja 47 z 57.
Wzorzec dekoratora dodaje zachowanie do obiektu, opakowując go innym obiektem o tym samym interfejsie. Dekorator przekazuje wywołania do opakowywanego obiektu i dodaje własną część, dzięki czemu dekoratory można nakładać jeden na drugi:
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"
}Wewnątrz main:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")Wynik:
espresso, milk, milk, syrup: 330Gdy dekoratory zmieniają wynik, ich kolejność ma znaczenie: każdy z nich działa na tym, co zwrócił zawarty w nim obiekt:
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()
}Wewnątrz main:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())Wynik:
[hi][hi]
[hihi]Za pomocą by dekorator zapisuje tylko te składowe, które zmienia, a pozostałe automatycznie przekazuje dalej:
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, "***")
}Wewnątrz main:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")Wynik:
notes.txt: the *** planDekoratory są łączone podczas działania programu, na przykład na podstawie wyborów klienta, podczas gdy podklasa ustala jedną kombinację na etapie pisania kodu. Trzy dodatki zaimplementowane jako podklasy wymagałyby osobnej klasy dla każdej kombinacji; jako dekoratory można je nakładać za pomocą fold na podstawie listy:
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
}Wewnątrz 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())Wynik:
370Wyzwanie
ŁatwyPizze są tworzone za pomocą dekoratorów dostarczonego interfejsu Pizza (z metodami price() i toppings()). Napisz Base (800, bez dodatków) oraz dekoratory Cheese (+150, dodaje cheese), Olives (+100, dodaje olives) i DoubleUp, który podwaja cenę wszystkiego, co się w nim znajduje, i powtarza jego dodatki dwukrotnie. Następnie napisz build(names), która zaczyna od Base i opakowuje go jednym dekoratorem na każdą nazwę, w podanej kolejności (cheese, olives lub double).
Dostarczony kod odczytuje wiersze zawierające nazwy oddzielone przecinkami lub plain, buduje każdą pizzę i wypisuje 1050: cheese, olives lub 800: plain.
Twój kod umieść w plikach Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt i Pizza.kt. Plik Main.kt zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
fun main() {
// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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(", "))
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasKonstruktory główneBloki init i walidacjaKonstruktory dodatkowePodsumowanie — karta biblioteczna4Dziedziczenie
Klasy otwarte i podklasyNadpisywanie składowychsuper i konstruktoryKlasy abstrakcyjnePodsumowanie – katalog pracowników7Klasy specjalne
Klasy danychKlasy wyliczenioweHierarchie zapieczętowaneequals, hashCode, toStringPodsumowanie — śledzenie zamówień10Wzorce projektowe, część 2
Delegowanie za pomocą byWzorzec dekoratoraWzorzec budowniczegoWzorzec stanu2Właściwości i hermetyzacja
Własne akcesoryModyfikatory widocznościPrywatne setterylazy i lateinitPowtórka – konto bankowe5Interfejsy
Deklarowanie interfejsówMetody domyślneWiele interfejsówInterfejsy jako typyPowtórzenie – metody płatności8Typy generyczne
Funkcje generyczneKlasy generyczneOgraniczenia typówKowariancja i kontrawariancja: in i outPodsumowanie — kolejka priorytetowa11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Kotlin