Beobachtermuster
Teil des Abschnitts Objektorientierte Programmierung der Swift-Journey von Coddy. Lektion 44 von 57.
Beim Beobachter-Muster führt ein Objekt (das Subjekt) eine Liste von Beobachtern und benachrichtigt sie alle, wenn etwas geschieht. Das Subjekt muss nicht wissen, was die Beobachter tun:
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()Ausgabe:
logger saw tapped
counter saw tappedBeobachter können auch Objekte sein, die einem Protokoll entsprechen. Das Subjekt ruft die Protokollmethode für jeden registrierten Beobachter auf:
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 = 120Ausgabe:
AAPL above 100: 120Beobachter benötigen normalerweise eine Möglichkeit, das Lauschen zu beenden. Wenn bei der Registrierung eine ID (oder ein Token) zurückgegeben wird, kann der Aufrufer seinen Beobachter später entfernen:
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")Ausgabe:
A got login
B got login
B got logout@escaping kennzeichnet eine Closure, die gespeichert und später aufgerufen wird, nachdem die Funktion, die sie empfangen hat, zurückgekehrt ist. Das Beobachtermuster hält das Subjekt und die Beobachter lose gekoppelt: Neue Beobachter können hinzugefügt werden, ohne das Subjekt zu ändern.
func onTap(_ handler: @escaping (String) -> Void) {
handlers.append(handler) // gespeichert: muss @escaping sein
}Aufgabe
EinfachVervollständige die Klasse Newsletter, ein Subjekt, das Ausgaben an Abonnenten sendet. subscribe(name:handler:) speichert eine Closure unter einem Namen (eine erneute Anmeldung mit demselben Namen ersetzt sie), unsubscribe(name:) entfernt sie, und publish(_ title:) ruft jeden Handler in alphabetischer Reihenfolge der Namen auf und gibt zurück, wie viele benachrichtigt wurden.
Der bereitgestellte Code liest die Befehle sub <name>, unsub <name> oder pub <title>. Der Handler jedes Abonnenten gibt <name> reads <title> aus; nach jedem pub gibt der Code sent to 2 aus.
Dein Code gehört in Newsletter.swift. main.swift enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.
Probier es selbst
// Bereitgestellter Ein-/Ausgabecode: belasse ihn wie er ist
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]))")
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenMethoden und selfInitialisiererKlassen sind ReferenzenRückblick – Bibliotheksausweis4Kapselung
ZugriffskontrolleVon außen schreibgeschütztZustand schützenFehlschlagende InitialisiererRückblick – Bankkonto7Polymorphismus und Generika
PolymorphismusTypumwandlungGenerische FunktionenGenerische TypenZusammenfassung – Shape-Rechner2Strukturen und Werttypen
StrukturenWertsemantikVerändernde MethodenStruktur oder KlasseZusammenfassung – Inventarelement5Vererbung
UnterklassenMethoden überschreibenInitialisierer und superfinal und HierarchienRückblick – Mitarbeiterhierarchie8Standardprotokolle
CustomStringConvertibleEquatableComparableHashableRückblick – Benutzerdefiniertes Sortieren11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Swift-Playground