Pattern Observer
Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 44 di 57.
Nel pattern observer, un oggetto (il soggetto) mantiene un elenco di osservatori e li avvisa tutti quando succede qualcosa. Il soggetto non deve sapere cosa fanno gli osservatori:
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()Output:
logger saw tapped
counter saw tappedGli osservatori possono anche essere oggetti conformi a un protocollo. Il soggetto chiama il metodo del protocollo su ogni osservatore registrato:
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 = 120Output:
AAPL above 100: 120Di solito gli observer hanno bisogno di un modo per smettere di ascoltare. Restituire un id (o token) al momento della registrazione permette a chi chiama di rimuovere in seguito il proprio observer:
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")Output:
A got login
B got login
B got logout@escaping contrassegna una closure che viene memorizzata e chiamata in seguito, dopo che la funzione che l'ha ricevuta ha terminato l'esecuzione. Il pattern Observer mantiene il soggetto e gli osservatori debolmente collegati: è possibile aggiungere nuovi osservatori senza modificare il soggetto.
func onTap(_ handler: @escaping (String) -> Void) {
handlers.append(handler) // memorizzato: deve essere @escaping
}Sfida
FacileCompleta la classe Newsletter, un soggetto che invia le notizie ai suoi iscritti. subscribe(name:handler:) memorizza una closure associandola a un nome (iscriversi di nuovo con lo stesso nome la sostituisce), unsubscribe(name:) la rimuove e publish(_ title:) chiama ogni gestore in ordine alfabetico di nome e restituisce quanti sono stati avvisati.
Il codice fornito legge i comandi sub <name>, unsub <name> oppure pub <title>. Il gestore di ogni iscritto stampa <name> reads <title>; dopo ogni pub il codice stampa sent to 2.
Il tuo codice va in Newsletter.swift. main.swift contiene il codice fornito per l'input/output e non può essere modificato.
Provalo tu
// Supplied input/output code: keep it as it is
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]))")
}
}
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
1Classi e oggetti
Lavorare con i fileDefinire le classiMetodi e selfInizializzatoriLe classi sono riferimentiRiepilogo - Tessera della biblioteca4Incapsulamento
Controllo degli accessiSola lettura dall’esternoProteggere lo statoInizializzatori che possono fallireRiepilogo - Conto bancario7Polimorfismo e tipi generici
PolimorfismoCasting dei tipiFunzioni genericheTipi genericiRipasso - Calcolatore di forme2Struct e tipi valore
Le structSemantica dei valoriMetodi mutantiStruct o classeRiepilogo - Articolo di inventario5Ereditarietà
SottoclassiSovrascrivere i metodiInizializzatori e superfinal e gerarchieRiepilogo - Gerarchia dei dipendenti8Protocolli standard
CustomStringConvertibleEquatableComparableHashableRiepilogo - Ordinamento personalizzato11Progetto: Gestione della biblioteca
Libri e utentiPrestito dei libriEsercitati da solo: Playground Swift