Menu
Coddy logo textTech

Patron Observateur

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

Dans le modèle observer, un objet (le subject) conserve une liste d’observateurs et les avertit tous lorsqu’un événement se produit. Le sujet n’a pas besoin de savoir ce que font les observateurs :

final class Button {
    private var handlers: [(String) -> Void] = []

    func onTap(_ handler: @escaping (String) -> Void) {
        handlers.append(handler)
    }

    func tap() {
        for h in handlers { h("tapped") }
    }
}

let b = Button()
b.onTap { print("logger saw \($0)") }
b.onTap { print("counter saw \($0)") }
b.tap()

Sortie :

logger saw tapped
counter saw tapped

Les observateurs peuvent également être des objets qui se conforment à un protocole. Le sujet appelle la méthode du protocole sur chaque observateur enregistré :

protocol StockObserver: AnyObject {
    func priceChanged(_ symbol: String, to price: Int)
}

final class Stock {
    let symbol: String
    private var observers: [StockObserver] = []
    var price: Int { didSet { observers.forEach { $0.priceChanged(symbol, to: price) } } }
    init(symbol: String, price: Int) { self.symbol = symbol; self.price = price }
    func add(_ o: StockObserver) { observers.append(o) }
}

final class Alert: StockObserver {
    func priceChanged(_ s: String, to p: Int) { if p > 100 { print("\(s) above 100: \(p)") } }
}

let apple = Stock(symbol: "AAPL", price: 90)
apple.add(Alert())
apple.price = 95
apple.price = 120

Sortie :

AAPL above 100: 120

Les observateurs ont généralement besoin d’un moyen de cesser d’écouter. Le fait de renvoyer un identifiant (ou un jeton) lors de l’enregistrement permet à l’appelant de supprimer son observateur ultérieurement :

final class EventBus {
    private var listeners: [Int: (String) -> Void] = [:]
    private var nextId = 0

    func subscribe(_ f: @escaping (String) -> Void) -> Int {
        nextId += 1
        listeners[nextId] = f
        return nextId
    }
    func unsubscribe(_ id: Int) { listeners[id] = nil }
    func publish(_ event: String) {
        for id in listeners.keys.sorted() { listeners[id]!(event) }
    }
}

let bus = EventBus()
let a = bus.subscribe { print("A got \($0)") }
_ = bus.subscribe { print("B got \($0)") }
bus.publish("login")
bus.unsubscribe(a)
bus.publish("logout")

Sortie :

A got login
B got login
B got logout

@escaping indique une closure qui est stockée et appelée ultérieurement, après le retour de la fonction qui l’a reçue. Le pattern Observer maintient le sujet et les observateurs faiblement couplés : de nouveaux observateurs peuvent être ajoutés sans modifier le sujet.

func onTap(_ handler: @escaping (String) -> Void) {
    handlers.append(handler)   // stocké : doit être @escaping
}
challenge icon

Défi

Facile

Complétez la classe Newsletter, un sujet qui envoie des numéros aux abonnés. subscribe(name:handler:) stocke une closure sous un nom (une nouvelle inscription avec le même nom la remplace), unsubscribe(name:) la supprime, et publish(_ title:) appelle chaque gestionnaire dans l’ordre alphabétique des noms et renvoie le nombre de gestionnaires qui ont été notifiés.

Le code fourni lit les commandes sub <name>, unsub <name> ou pub <title>. Le gestionnaire de chaque abonné affiche <name> reads <title> ; après chaque pub, le code affiche sent to 2.

Votre code doit être placé dans Newsletter.swift. main.swift contient le code fourni pour les entrées et sorties 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) }
let news = Newsletter()
for cmd in input {
    let p = cmd.split(separator: " ", maxSplits: 1).map(String.init)
    switch p[0] {
    case "sub":
        let name = p[1]
        news.subscribe(name: name) { print("\(name) reads \($0)") }
    case "unsub": news.unsubscribe(name: p[1])
    default: print("sent to \(news.publish(p[1]))")
    }
}
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