Menu
Coddy logo textTech

Wzorzec dekorator

Część sekcji Programowanie obiektowe ścieżki Swift w Coddy. Lekcja 47 z 57.

Wzorzec dekoratora dodaje zachowanie do obiektu, opakowując go w inny obiekt obsługujący ten sam protokół. Opakowujący obiekt przekazuje wywołanie do opakowanego obiektu i dodaje własną część przed nim lub po nim:

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

Wynik:

espresso + milk
250

Ponieważ dekorator sam w sobie jest obiektem Coffee, dekoratory mogą opakowywać się nawzajem w dowolnej kombinacji i kolejności, bez tworzenia podklasy dla każdej kombinacji:

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

Wynik:

espresso + milk + milk + syrup
330

Dekoratory sprawdzają się w przypadku dodatków przekrojowych, takich jak logowanie, buforowanie czy walidacja, otaczających istniejący komponent, który pozostaje niezmieniony:

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

Wynik:

fetching 7
row 7

Kod korzystający z obiektu widzi tylko protokół, więc nie może odróżnić obiektu udekorowanego od zwykłego. Dzięki temu wzorzec można bezpiecznie dodać później.

func printReceipt(_ c: any Coffee) {      // działa dla Espresso i każdej wersji opakowanej
    print("\(c.label()): \(c.cost())")
}
challenge icon

Wyzwanie

Łatwy

Pizze buduje się za pomocą dekoratorów. Protokół Pizza ma metody price() i toppings() (tablicę nazw). Base kosztuje 800 i nie ma dodatków. Zaimplementuj dekoratory Cheese (+150), Olives (+100) oraz Double (podwaja cenę wszystkiego, co znajduje się wewnątrz, i powtarza dodatki dwukrotnie). Uzupełnij build(_:), która zaczyna od Base i opakowuje ją po kolei jednym dekoratorem dla każdej nazwy.

Dostarczony kod odczytuje wiersze z nazwami dodatków oddzielonymi przecinkami (lub pusty wiersz), buduje każdą pizzę i wypisuje 1050: cheese, olives lub 800: plain.

Twój kod umieść w plikach Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift i Functions.swift. main.swift zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Edytor online Swift