نمط 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تعدد الأشكال والأنواع العامة
تعدد الأشكالتحويل الأنواعالدوال العامةالأنواع العامةمراجعة - حاسبة الأشكالتدرّب بنفسك: Playground لـ Swift