Menu
Coddy logo textTech

Pattern Decorator

Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 47 di 57.

Il pattern decorator aggiunge comportamento a un oggetto racchiudendolo in un altro oggetto con lo stesso protocollo. Il wrapper inoltra la chiamata all'oggetto racchiuso e aggiunge la propria parte prima o dopo:

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

Output:

espresso + milk
250

Poiché un decoratore è a sua volta un Coffee, i decoratori possono avvolgersi a vicenda in qualsiasi combinazione e ordine, senza una sottoclasse per ogni combinazione:

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

Output:

espresso + milk + milk + syrup
330

I decoratori si adattano ad aggiunte trasversali come la registrazione, la memorizzazione nella cache o la convalida applicate a un componente esistente, che rimane invariato:

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

Output:

fetching 7
row 7

Il codice che usa l'oggetto vede solo il protocollo, quindi non può distinguere un oggetto decorato da uno semplice. È questo che rende sicuro aggiungere il pattern in un secondo momento.

func printReceipt(_ c: any Coffee) {      // funziona per Espresso e per qualsiasi versione avvolta
    print("\(c.label()): \(c.cost())")
}
challenge icon

Sfida

Facile

Le pizze vengono costruite con i decoratori. Il protocollo Pizza ha price() e toppings() (un array di nomi). Base costa 800 e non ha condimenti. Implementa i decoratori Cheese (+150), Olives (+100) e Double (raddoppia il prezzo di tutto ciò che contiene e ripete i condimenti due volte). Completa build(_:), che parte da Base e lo avvolge con un decoratore per ogni nome, nell'ordine indicato.

Il codice fornito legge righe di nomi di condimenti separati da virgole (o una riga vuota), costruisce ogni pizza e stampa 1050: cheese, olives o 800: plain.

Il tuo codice va inserito in Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift e Functions.swift. main.swift contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

// Código de entrada/salida proporcionado: déjalo tal como está
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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Playground Swift