Шаблон Observer
Часть раздела Объектно-ориентированное программирование путешествия по Swift на Coddy. Урок 44 из 57.
В паттерне наблюдатель объект (субъект) хранит список наблюдателей и уведомляет их всех, когда что-то происходит. Субъекту не нужно знать, что делают наблюдатели:
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()Вывод:
logger saw tapped
counter saw tappedНаблюдателями также могут быть объекты, соответствующие протоколу. Субъект вызывает метод протокола у каждого зарегистрированного наблюдателя:
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Вывод:
AAPL above 100: 120Наблюдателям обычно нужен способ прекратить прослушивание. Возврат идентификатора (или токена) при регистрации позволяет вызывающему коду позже удалить своего наблюдателя:
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")Вывод:
A got login
B got login
B got logout@escaping обозначает замыкание, которое сохраняется и вызывается позже, после возврата функции, получившей его. Паттерн «наблюдатель» поддерживает слабую связанность субъекта и наблюдателей: можно добавлять новых наблюдателей, не изменяя субъект.
func onTap(_ handler: @escaping (String) -> Void) {
handlers.append(handler) // сохранён: должен быть @escaping
}Задание
ЛегкоЗавершите класс Newsletter — объект, который отправляет выпуски подписчикам. subscribe(name:handler:) сохраняет замыкание под именем (повторная подписка с тем же именем заменяет его), unsubscribe(name:) удаляет его, а publish(_ title:) вызывает каждый обработчик в алфавитном порядке имён и возвращает количество получивших уведомление.
Предоставленный код считывает команды sub <name>, unsub <name> или pub <title>. Обработчик каждого подписчика выводит <name> reads <title>; после каждой команды pub код выводит sent to 2.
Ваш код должен находиться в Newsletter.swift. В main.swift содержится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
// Предоставленный код ввода/вывода: оставьте его как есть
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]))")
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовМетоды и selfИнициализаторыКлассы — это ссылкиПовторение — читательский билет4Инкапсуляция
Контроль доступаТолько чтение извнеЗащита состоянияИнициализаторы с возможностью сбояПовторение — банковский счёт7Полиморфизм и обобщения
ПолиморфизмПриведение типовОбобщённые функцииОбобщённые типыПовторение — калькулятор фигур10Паттерны проектирования. Часть 2
Паттерн делегированияПаттерн декораторПаттерн строительПаттерн состояния2Структуры и типы-значения
СтруктурыСемантика значенийИзменяющие методыСтруктура или классПовторение — элемент инвентаря5Наследование
ПодклассыПереопределение методовИнициализаторы и superfinal и иерархииПовторение — иерархия сотрудников8Стандартные протоколы
CustomStringConvertibleEquatableComparableHashableПовторение — пользовательская сортировка11Проект: Управление библиотекой
Книги и читателиВыдача книгПотренируйтесь самостоятельно: Песочница Swift