نمط Observer
جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 44 من 57.
يتيح نمط المراقب للكائنات الاشتراك في أحداث كائن آخر، وهو الموضوع. يحتفظ الموضوع بقائمة من المراقبين ويُخطر كل واحد منهم عند حدوث شيء ما، من دون أن يعرف ماذا سيفعلون به:
interface PriceListener {
fun priceChanged(symbol: String, price: Int)
}
class Stock(val symbol: String) {
private val listeners = mutableListOf<PriceListener>()
var price = 0
set(value) {
field = value
for (l in listeners) l.priceChanged(symbol, value)
}
fun subscribe(l: PriceListener) { listeners.add(l) }
}
class Ticker : PriceListener {
override fun priceChanged(symbol: String, price: Int) = println("$symbol is now $price")
}داخل main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95الناتج:
ACME is now 120
ACME is now 95في Kotlin، يكون المراقب غالبًا دالة عادية. يخزّن الكائن الخاضع للمراقبة تعبيرات lambda ويستدعي كلًّا منها، لذلك لا يحتاج المراقب الجديد إلى فئة:
class Button(val label: String) {
private val handlers = mutableListOf<(String) -> Unit>()
fun onClick(handler: (String) -> Unit) { handlers.add(handler) }
fun click() { for (h in handlers) h(label) }
}داخل main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")الناتج:
Save clicked
Save clicked
clicks: 2قد يرغب أحد المراقبين في التوقف عن الاستماع. إذا كانت subscribe تُرجع دالة تزيل المراقب مرة أخرى، فيمكن للمتصل الإلغاء من دون الوصول إلى قائمة الموضوع:
class Feed {
private val readers = mutableListOf<(String) -> Unit>()
fun subscribe(reader: (String) -> Unit): () -> Unit {
readers.add(reader)
return { readers.remove(reader) }
}
fun post(text: String) { for (r in readers.toList()) r(text) }
}داخل main:
val feed = Feed()
val cancel = feed.subscribe { println("ada reads: $it") }
feed.subscribe { println("bo reads: $it") }
feed.post("hello")
cancel()
feed.post("again")الناتج:
ada reads: hello
bo reads: hello
bo reads: againتحتوي المكتبة القياسية على خاصية قابلة للمراقبة. تعمل Delegates.observable(initial) { property, old, new -> ... } على تشغيل اللامبدا بعد كل عملية إسناد؛ يشرح الفصل 10 الكلمة المفتاحية by التي تستخدمها:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}داخل main:
val t = Thermostat()
t.target = 22
t.target = 18الناتج:
target changed from 20 to 22
target changed from 22 to 18التحدي
سهلتُخطِر WeatherStation المراقبين الذين يطبّقون TemperatureObserver المقدَّم. اكتب attach(observer) وdetach(observer) وrecord(temp)، بحيث تُخطِر كل مراقب مرتبط، ولكن فقط عندما تختلف درجة الحرارة عن القراءة السابقة (تُخطِر القراءة الأولى دائمًا). اكتب ثلاثة مراقبين: يحتفظ Display بالأسطر display: 21 في lines، ويحتفظ Alarm(threshold) بالأسطر alarm: 31 > 30 في alerts لدرجات الحرارة التي تتجاوز العتبة، ويُرجع Stats القيمة min 18, max 31, updates 4 من summary() (أو no data).
يقرأ الكود المقدَّم عتبة الإنذار ثم درجات الحرارة أو detach alarm / attach alarm، ويربط المراقبين الثلاثة جميعًا في البداية، ثم يطبع أسطر العرض والتنبيهات والملخّص في النهاية.
ضع كودك في WeatherStation.kt وDisplay.kt وAlarm.kt وStats.kt وTemperatureObserver.kt. يحتوي Main.kt على كود الإدخال/الإخراج المقدَّم ولا يمكن تعديله.
جرّب بنفسك
fun main() {
// كود الإدخال/الإخراج المُزوَّد: اتركه كما هو
val input = generateSequence(::readLine).toList()
val station = WeatherStation()
val display = Display()
val alarm = Alarm(input[0].toInt())
val stats = Stats()
station.attach(display)
station.attach(alarm)
station.attach(stats)
for (cmd in input.drop(1)) {
when (cmd) {
"detach alarm" -> station.detach(alarm)
"attach alarm" -> station.attach(alarm)
else -> station.record(cmd.toInt())
}
}
for (line in display.lines) println(line)
for (line in alarm.alerts) println(line)
println(stats.summary())
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتالمنشئات الأساسيةكتل init والتحقق من الصحةالمنشئات الثانويةمراجعة - بطاقة المكتبة4الوراثة
الأصناف المفتوحة والأصناف الفرعيةتجاوز الأعضاءsuper والمنشئاتالأصناف المجرّدةمراجعة - دليل الموظفينتدرّب بنفسك: Playground لـ Kotlin