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 tappedLes 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 = 120Sortie :
AAPL above 100: 120Les 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
}Défi
FacileComplé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]))")
}
}
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
1Classes et objets
Travailler avec des fichiersDéfinir des classesMéthodes et selfInitialiseursLes classes sont des référencesRécapitulatif - Carte de bibliothèque4Encapsulation
Contrôle d’accèsLecture seule depuis l’extérieurProtection de l’étatInitialiseurs pouvant échouerRécapitulatif - Compte bancaire7Polymorphisme et génériques
PolymorphismeConversion de typeFonctions génériquesTypes génériquesRécapitulatif - Calculateur de formes2Structures et types valeur
StructuresSémantique de valeurMéthodes mutantesStructure ou classeRécapitulatif - article d’inventaire5Héritage
Sous-classesRedéfinition des méthodesInitialiseurs et superfinal et hiérarchiesRécapitulatif - hiérarchie des employés8Protocoles standard
CustomStringConvertibleEquatableComparableHashableRécapitulatif - Tri personnalisé11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Swift