Mẫu Observer
Thuộc phần Lập trình hướng đối tượng trong hành trình Swift của Coddy. Bài học 44 trên 57.
Trong mẫu thiết kế observer, một đối tượng (subject) giữ một danh sách các observer và thông báo cho tất cả khi có chuyện xảy ra. Subject không cần biết các observer làm gì:
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()Đầu ra:
logger saw tapped
counter saw tappedObserver cũng có thể là các đối tượng tuân theo một giao thức. Đối tượng chủ thể gọi phương thức của giao thức trên từng observer đã đăng ký:
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Đầu ra:
AAPL above 100: 120Các đối tượng quan sát thường cần cách ngừng lắng nghe. Trả về một id (hoặc token) khi đăng ký để bên gọi có thể xóa đối tượng quan sát đó sau này:
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")Đầu ra:
A got login
B got login
B got logout@escaping đánh dấu một closure được lưu lại và gọi sau, khi hàm nhận closure đó đã trả về. Mẫu observer giúp đối tượng chủ thể và các observer ít phụ thuộc vào nhau: bạn có thể thêm observer mới mà không cần thay đổi đối tượng chủ thể.
func onTap(_ handler: @escaping (String) -> Void) {
handlers.append(handler) // được lưu lại: phải có @escaping
}Thử thách
DễHoàn thiện lớp Newsletter, một đối tượng gửi các bản tin cho người đăng ký. subscribe(name:handler:) lưu một closure theo tên (nếu đăng ký lại với cùng tên, closure mới sẽ thay thế closure cũ), unsubscribe(name:) xóa closure đó, còn publish(_ title:) gọi tất cả handler theo thứ tự tên trong bảng chữ cái và trả về số người đã nhận thông báo.
Code được cung cấp đọc các lệnh sub <name>, unsub <name> hoặc pub <title>. Handler của mỗi người đăng ký in ra <name> reads <title>; sau mỗi lệnh pub, code in ra sent to 2.
Code của bạn nằm trong Newsletter.swift. main.swift chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.
Tự làm thử
// Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
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]))")
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpPhương thức và selfHàm khởi tạoLớp là tham chiếuÔn tập: Thẻ thư viện2Struct và kiểu giá trị
StructNgữ nghĩa giá trịPhương thức thay đổiStruct hay classÔn tập: mặt hàng trong kho5Kế thừa
Lớp conGhi đè phương thứcHàm khởi tạo và superfinal và hệ phân cấpÔn tập: hệ phân cấp nhân viên8Các giao thức tiêu chuẩn
CustomStringConvertibleEquatableComparableHashableÔn tập: Sắp xếp tùy chỉnhTự luyện tập: Playground Swift