Patrón Observer
Parte de la sección Programación orientada a objetos del Journey de Swift de Coddy. Lección 44 de 57.
En el patrón de observador, un objeto (el sujeto) mantiene una lista de observadores y notifica a todos cuando ocurre algo. El sujeto no necesita saber qué hacen los observadores:
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()Salida:
logger saw tapped
counter saw tappedLos observadores también pueden ser objetos que cumplen un protocolo. El sujeto llama al método del protocolo en cada observador registrado:
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 = 120Salida:
AAPL above 100: 120Los observadores normalmente necesitan una forma de dejar de escuchar. Devolver un id (o token) desde el registro permite que quien llama elimine su observador más adelante:
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")Salida:
A got login
B got login
B got logout@escaping marca un cierre que se almacena y se llama más tarde, después de que la función que lo recibió haya retornado. El patrón observador mantiene el sujeto y los observadores débilmente conectados: se pueden añadir nuevos observadores sin cambiar el sujeto.
func onTap(_ handler: @escaping (String) -> Void) {
handlers.append(handler) // almacenado: debe ser @escaping
}Desafío
FácilCompleta la clase Newsletter, un sujeto que envía ediciones a los suscriptores. subscribe(name:handler:) almacena un cierre bajo un nombre (suscribirse de nuevo con el mismo nombre lo reemplaza), unsubscribe(name:) lo elimina y publish(_ title:) llama a cada controlador en orden alfabético por nombre y devuelve cuántos recibieron la notificación.
El código proporcionado lee los comandos sub <name>, unsub <name> o pub <title>. El controlador de cada suscriptor imprime <name> reads <title>; después de cada pub, el código imprime sent to 2.
Tu código debe ir en Newsletter.swift. main.swift contiene el código de entrada/salida proporcionado y no se puede editar.
Pruébalo tú mismo
// Código de entrada/salida suministrado: déjalo tal como está
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]))")
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Clases y objetos
Trabajar con archivosDefinir clasesMétodos y selfInicializadoresLas clases son referenciasRepaso: tarjeta de biblioteca4Encapsulación
Control de accesoSolo lectura desde fueraProtección del estadoInicializadores que pueden fallarRepaso - Cuenta bancaria7Polimorfismo y genéricos
PolimorfismoConversión de tiposFunciones genéricasTipos genéricosRepaso - Calculadora de formas2Structs y tipos por valor
StructsSemántica de valoresMétodos mutablesStruct o claseRepaso - elemento del inventario5Herencia
SubclasesSobrescritura de métodosInicializadores y superfinal y jerarquíasRepaso: jerarquía de empleados8Protocolos estándar
CustomStringConvertibleEquatableComparableHashableRepaso - Ordenamiento personalizado11Proyecto: Gestión de bibliotecas
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Swift