Menu
Coddy logo textTech

Dekorierermuster

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

Das Dekorator-Muster fügt einem Objekt Verhalten hinzu, indem es in ein anderes Objekt mit demselben Protokoll eingeschlossen wird. Der Wrapper leitet den Aufruf an das eingeschlossene Objekt weiter und fügt seinen eigenen Teil davor oder danach hinzu:

protocol Coffee {
    func cost() -> Int
    func label() -> String
}

struct Espresso: Coffee {
    func cost() -> Int { 200 }
    func label() -> String { "espresso" }
}

struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}

let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())

Ausgabe:

espresso + milk
250

Da ein Decorator selbst ein Coffee ist, können Decorators einander in beliebiger Kombination und Reihenfolge umschließen, ohne für jede Kombination eine Unterklasse zu benötigen:

protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 30 }
    func label() -> String { base.label() + " + syrup" }
}

let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())

Ausgabe:

espresso + milk + milk + syrup
330

Dekoratoren eignen sich für übergreifende Ergänzungen wie Protokollierung, Caching oder Validierung rund um eine bestehende Komponente, die unverändert bleibt:

protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }

struct Logged: DataSource {
    let inner: any DataSource
    func fetch(_ id: Int) -> String {
        print("fetching \(id)")
        return inner.fetch(id)
    }
}

let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))

Ausgabe:

fetching 7
row 7

Der Code, der das Objekt verwendet, sieht nur das Protokoll und kann daher ein dekoriertes Objekt nicht von einem einfachen unterscheiden. Das macht es sicher, das Muster später hinzuzufügen.

func printReceipt(_ c: any Coffee) {      // funktioniert für Espresso und für jede umhüllte Version
    print("\(c.label()): \(c.cost())")
}
challenge icon

Aufgabe

Einfach

Pizzen werden mit Dekoratoren erstellt. Das Protokoll Pizza verfügt über price() und toppings() (ein Array mit Namen). Base kostet 800 und hat keinen Belag. Implementiere die Dekoratoren Cheese (+150), Olives (+100) und Double (verdoppelt den Preis von allem, was sich darin befindet, und wiederholt den Belag zweimal). Vervollständige build(_:), das mit Base beginnt und es in der angegebenen Reihenfolge mit einem Dekorator pro Namen umschließt.

Der bereitgestellte Code liest Zeilen mit durch Kommas getrennten Belagnamen (oder eine leere Zeile), erstellt für jede Zeile eine Pizza und gibt 1050: cheese, olives oder 800: plain aus.

Dein Code gehört in Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift und Functions.swift. main.swift enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

// Bereitgestellter Ein-/Ausgabe-Code: belasse ihn wie er ist
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
    let p = build(names)
    let tops = p.toppings()
    print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
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: Swift-Playground