Menu
Coddy logo textTech

Patron décorateur

Fait partie de la section Programmation orientée objet du Journey Swift de Coddy. Leçon 47 sur 57.

Le modèle décorateur ajoute un comportement à un objet en l’enveloppant dans un autre objet utilisant le même protocole. L’enveloppe transmet l’appel à l’objet enveloppé et ajoute sa propre partie avant ou après :

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

Sortie :

espresso + milk
250

Comme un décorateur est lui-même un Coffee, les décorateurs peuvent s’imbriquer les uns dans les autres dans n’importe quelle combinaison et dans n’importe quel ordre, sans avoir besoin d’une sous-classe pour chaque combinaison :

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

Sortie :

espresso + milk + milk + syrup
330

Les décorateurs conviennent aux ajouts transversaux tels que la journalisation, la mise en cache ou la validation autour d’un composant existant, qui reste inchangé :

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

Sortie :

fetching 7
row 7

Le code qui utilise l’objet ne voit que le protocole, il ne peut donc pas distinguer un objet décoré d’un objet ordinaire. C’est ce qui rend le modèle sûr à ajouter ultérieurement.

func printReceipt(_ c: any Coffee) {      // fonctionne pour Espresso et pour toute version encapsulée
    print("\(c.label()): \(c.cost())")
}
challenge icon

Défi

Facile

Les pizzas sont construites à l’aide de décorateurs. Le protocole Pizza possède price() et toppings() (un tableau de noms). Base coûte 800 et ne comporte aucune garniture. Implémentez les décorateurs Cheese (+150), Olives (+100) et Double (double le prix de tout ce qui se trouve à l’intérieur et répète deux fois ses garnitures). Complétez build(_:), qui commence par Base et l’enveloppe avec un décorateur par nom, dans l’ordre.

Le code fourni lit des lignes contenant des noms de garnitures séparés par des virgules (ou une ligne vide), construit chaque pizza et affiche 1050: cheese, olives ou 800: plain.

Votre code doit se trouver dans Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift et Functions.swift. main.swift contient le code fourni pour l’entrée et la sortie et ne peut pas être modifié.

Essayez vous-même

// Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Playground Swift