Mẫu Observer
Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 44 trên 57.
Mẫu observer cho phép các đối tượng đăng ký nhận sự kiện từ một đối tượng khác, gọi là subject. Subject lưu danh sách observer và thông báo cho từng đối tượng khi có sự việc xảy ra, mà không cần biết chúng sẽ làm gì với thông báo đó:
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")
}Bên trong main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Đầu ra:
ACME is now 120
ACME is now 95Trong Kotlin, observer thường là một hàm đơn giản. Đối tượng chủ thể lưu các lambda và gọi từng lambda, nên observer mới không cần lớp nào:
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) }
}Bên trong main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Đầu ra:
Save clicked
Save clicked
clicks: 2Một observer có thể muốn ngừng lắng nghe. Nếu subscribe trả về một hàm xóa observer đó, bên gọi có thể hủy đăng ký mà không cần truy cập vào danh sách của subject:
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) }
}Bên trong 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")Đầu ra:
ada reads: hello
bo reads: hello
bo reads: againThư viện chuẩn có thuộc tính có thể quan sát. Delegates.observable(initial) { property, old, new -> ... } chạy lambda sau mỗi lần gán; chương 10 giải thích từ khóa by mà nó sử dụng:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}Bên trong main:
val t = Thermostat()
t.target = 22
t.target = 18Đầu ra:
target changed from 20 to 22
target changed from 22 to 18Thử thách
DễWeatherStation thông báo cho các observer triển khai TemperatureObserver đã được cung cấp. Viết các hàm attach(observer), detach(observer) và record(temp). Hàm record(temp) thông báo cho mọi observer đã được gắn, nhưng chỉ khi nhiệt độ khác với lần đo trước đó (lần đo đầu tiên luôn gửi thông báo). Viết ba observer: Display lưu các dòng display: 21 trong lines, Alarm(threshold) lưu các dòng alarm: 31 > 30 trong alerts khi nhiệt độ cao hơn ngưỡng, và Stats trả về min 18, max 31, updates 4 từ summary() (hoặc no data).
Code đã cung cấp đọc ngưỡng báo động, sau đó đọc nhiệt độ hoặc detach alarm / attach alarm, gắn cả ba observer ngay từ đầu, rồi cuối cùng in các dòng hiển thị, cảnh báo và bản tóm tắt.
Đặt code của bạn trong WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt và TemperatureObserver.kt. Main.kt chứa code đầu vào/đầu ra đã cung cấp và bạn không thể chỉnh sửa tệp này.
Tự làm thử
fun main() {
// Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
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())
}
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ớpHàm khởi tạo chínhKhối init và kiểm tra tính hợp lệHàm khởi tạo phụÔn tập: thẻ thư viện4Kế thừa
Lớp mở và lớp conGhi đè thành viênsuper và hàm khởi tạoLớp trừu tượngÔn tập: Danh bạ nhân viên2Thuộc tính và tính đóng gói
Trình truy cập tùy chỉnhBộ sửa đổi phạm vi truy cậpSetter riêng tưlazy và lateinitÔn tập: tài khoản ngân hàng5Giao diện
Khai báo giao diệnPhương thức mặc địnhNhiều giao diệnGiao diện như kiểu dữ liệuÔn tập: Phương thức thanh toánTự luyện tập: Playground Kotlin