1. Vi sao can huy dang ky
Khi lan dau tien ban viet co che su kien, thuong se co cam giac: “da dang ky — the la xong, cu de no chay mai”. Cam giac nay se ton tai dung cho toi luc ban “tam bat logging” vai lan, sau do ngo ngang vi no in ra gap ba. Hoac khi “handler tam thoi” van tiep tuc ton tai du kich ban da ket thuc. Huy dang ky khong phai la su cau ky cua kien truc su, ma la cach giu hanh vi chuong trinh co the du doan duoc va khong tich luy phan ung thua.
Hay tuong tuong du an console hoc tap cua chung ta (vi du: mot ung dung theo doi chi tieu), trong do service phat su kien “da them khoan chi”, va cac phan khac nhau cua he thong phan ung: audit ghi vao log, UI in thong bao, thong ke cap nhat bo dem.
data class Expense(val title: String, val amount: Int)
class ExpenseService {
val expenseAdded = Event<Expense>()
fun addExpense(title: String, amount: Int) {
val e = Expense(title, amount)
println("Added: $e") // Added: Expense(title=Coffee, amount=250)
expenseAdded.emit(e)
}
}
Cho den day thi moi thu deu dep. Nhung sau do “cuoc song” bat dau: audit thi ta muon bat/tat, thong bao ra console cung khong phai luc nao cung can, con handler phuc vu test thi nhat dinh phai tam thoi. Nghia la, ta can mot co che huy dang ky dung nghia.
2. Vi sao “chi can xoa listener” lai kho hon tuong
Khi ta co mot danh sach, nghe co ve hop ly: “chi can xoa khoi danh sach la xong”. Van de la listener la mot ham, va trong Kotlin ta co the luu ham nhu gia tri (lambda, tham chieu ham). Day la mot “sieu nang luc” rieng cua ngon ngu: ban co the dat ham vao bien, collection, truyen lam tham so va tra ve tu ham.
Nhung xoa mot ham khoi danh sach bang cach nao? Ban phai biet chinh xac ham nao da duoc them. Va day la cai bay kinh dien cho nguoi moi: “thi minh viet lai mot lambda giong het la duoc”.
Hay xem “thu nghiem ngay tho” trong thuc te:
val event = Event<String>()
fun main() {
event.subscribe { println("Got: $it") }
// Chung ta thu "huy dang ky" bang mot lambda giong giong...
// Nhung trong API nay thuc ra khong co ham do, va do khong phai ngau nhien.
}
Ke ca neu ta tao mot method unsubscribe(listener), ban se nhanh chong phat hien ra rang “cung mot lambda ve mat noi dung” khong dong nghia voi “cung mot lambda ve mat tham chieu”. Trong lap trinh, no giong nhu ban co gang tra lai cua hang “mot chiec ao y het”, nhung khong phai chiec ban da mua: nhin thi giong, nhung hoa don khong trung.
3. Ham nhu gia tri va tinh “tham chieu” cua lambda
Chinh xac thi ta dang dat gi vao listeners
Trong Kotlin, ham co kieu (type). Vi du, mot handler nhan vao Expense va khong tra ve gi co kieu (Expense) -> Unit. Day duoc goi la kieu ham; Kotlin dung chinh ky phap nay de mo ta “ham nhu gia tri”.
Hay “keo no xuong mat dat” bang mot vi du don gian de nao bo khoi chay di nghi:
fun main() {
val printer: (String) -> Unit = { text ->
println("PRINT: $text")
}
printer("Hello") // PRINT: Hello
}
O day printer la mot bien binh thuong, chi la ben trong no chua “mot doan code”. Va khi ta goi subscribe(listener), ta thuc chat dang dua cac gia tri kieu nay vao danh sach:
class Event<T> {
private val listeners = mutableListOf<(T) -> Unit>()
fun subscribe(listener: (T) -> Unit) {
listeners += listener
}
fun emit(value: T) {
for (l in listeners) l(value)
}
}
Chi tiet then chot: trong danh sach la cac doi tuong-ham cu the, va de xoa mot phan tu, ban can co tham chieu den chinh doi tuong do.
Vi sao “giong y het” nhung khong phai la cung mot lambda
Bay gio la mot diem quan trong, co the tiet kiem rat nhieu than kinh va ca phe (hoac tao them ly do de uong ca phe — tuy ban nhin nhan).
Hai lambda trong giong nhau thuong la hai doi tuong khac nhau:
fun main() {
val a: () -> Unit = { println("Hi") }
val b: () -> Unit = { println("Hi") }
println(a == b) // false
println(a === b) // false
}
Vi sao? Vi trong phan lon truong hop, lambda la mot doi tuong, va moi literal lambda moi se tao ra mot doi tuong moi. Neu ban muon xoa “dung handler do”, ban can co tham chieu den no: cung mot bien, cung mot doi tuong.
fun main() {
val a: () -> Unit = { println("Hi") }
val c = a
println(a === c) // true
}
Ket luan don gian nhung hoi “phu phang”: de huy dang ky, ban hoac phai luu tham chieu den handler o dau do, hoac thiet ke API sao cho no tu cung cap cach huy dang ky ma khong bat nguoi dung “doan xem minh da them listener nao tu mot gio truoc”.
Va day la luc nhan vat chinh xuat hien: token huy dang ky.
4. Token huy dang ky va hop dong cua subscribe
Y tuong ve token
Y tuong token huy dang ky nghe hoi “ngan hang”: “day la token cua ban, giu cho ky, dung lam mat”. Nhưng thuc chat no rat don gian va tien: subscribe(...) tra ve mot doi tuong (hoac mot ham) cho phep ban huy dang ky sau nay.
Ta se lam token toi da don gian: no la mot ham () -> Unit, ban chi can goi no de huy dang ky.
Tai sao lai la ham? Vi Kotlin ho tro luu va tra ve ham nhu gia tri, va no phu hop tu nhien voi bai toan nay.
Trien khai Event<T> voi token
Day la ban cap nhat cua Event<T>:
class Event<T> {
private val listeners = mutableListOf<(T) -> Unit>()
fun subscribe(listener: (T) -> Unit): () -> Unit {
listeners += listener
// Tra ve token huy dang ky: goi -> listener bi xoa
return { listeners.remove(listener) }
}
fun emit(value: T) {
for (l in listeners) l(value)
}
}
Dieu gi xay ra o day co ve “ma thuat”, nhung thuc ra rat hop ly:
Ham ma ta tra ve ({ listeners.remove(listener) }) bat (capture) hai thu tu ngu canh ben ngoai: listeners va listener. Day chinh la closure: ham duoc tra ve “nho” can xoa cai gi.
Su dung token
Bay gio code phia nguoi dung tro nen rat “nguoi”: dang ky — nhan token — sau do goi token khi can.
fun main() {
val event = Event<String>()
val unsubscribe = event.subscribe { text ->
println("Got: $text")
}
event.emit("A") // Got: A
unsubscribe()
event.emit("B") // (khong in gi)
}
Hay de y: ta khong “ngui” lambda de tim, khong so sanh “giong nhau ve mat chu”, khong chui vao trong Event de dung toi field private (va cam on Kotlin vi khong cho phep). Ta chi can giu token.
Huy dang ky lap lai khong nen lam hong chuong trinh
Mot cau hoi rat thuong gap: “neu minh lo tay goi token hai lan thi sao?”. Ly tuong nhat la: “khong sao ca”.
Phien ban listeners.remove(listener) da du an toan: neu phan tu khong con, remove chi tra ve false va khong bi crash. Day la “bonus” tien loi tu collection tieu chuan.
Thu kiem tra hanh vi bang mot vi du nho:
fun main() {
val e = Event<Int>()
val unsub = e.subscribe { println("x=$it") }
unsub()
unsub() // goi lai lan nua — ok, chi la khong con gi de xoa
}
Do chinh la hop dong tot cho token: co the goi hai lan (hoac nhieu lan), va chuong trinh van binh thuong.
So do nho: subscribe tra ve gi
Doi khi nhin no nhu mot dong du lieu se de hieu hon “no tu nho bang cach nao do”.
flowchart TD
A["subscribe(listener)"] --> B["listeners += listener"]
B --> C["return unsubscribeToken"]
C --> D["unsubscribeToken()"]
D --> E["listeners.remove(listener)"]
Y nghia so do rat don gian: token khong phai “ID dang ky trong database”. No chi la mot ham dong (closure) bat cac bien can thiet va co the xoa listener.
5. Vi du: “bat audit / tat audit”
Bay gio hay ap dung co che nay vao ung dung theo doi chi tieu. Gia su audit la mot subscriber in ra console dong “AUDIT: ...”. Ta muon bat/tat no bang lenh (hoac setting), ma khong them handler lap lai moi lan.
Lam thang tay nhat: luu token huy dang ky vao mot bien.
class AuditFeature(private val service: ExpenseService) {
private var unsubscribe: (() -> Unit)? = null
fun enable() {
if (unsubscribe != null) return
unsubscribe = service.expenseAdded.subscribe { e ->
println("AUDIT: added ${e.title} for ${e.amount}") // AUDIT: added Coffee for 250
}
}
fun disable() {
unsubscribe?.invoke()
unsubscribe = null
}
}
O day co hai hieu ung rat de chiu. Thu nhat, enable() tro thanh idempotent: neu audit da bat, goi lan hai se khong dang ky them. Thu hai, disable() an toan: neu audit da tat, unsubscribe se la null, va se khong co van de gi.
6. So sanh cac cach tiep can va dang ky trung lap
Vi sao token tot hon unsubscribe(listener)
Doi khi ta muon lam API “nguoi lon” hon va them method unsubscribe(listener). Ve mat hinh thuc thi co the, nhung gan nhu luc nao cung bat tien: ban phai luu listener o dau do, va nguoi moi thuong xuyen tao “mot lambda moi giong y het” roi thac mac vi sao huy dang ky khong hoat dong.
Token huy dang ky loai bo van de: khong can tim listener, token da “nho” can xoa cai gi.
| Cach tiep can | Trong code trong se ra sao | Bat tien trong doi that |
|---|---|---|
| Huy dang ky “theo listener” | |
Phai luu tham chieu listener va truyen vao dung doi tuong do, neu khong se khong xoa duoc |
| Huy dang ky “theo token” | |
Phai luu token, nhung thuong don gian va de hieu hon (mot bien) |
Neu cung mot listener duoc dang ky hai lan
Day la mot manh thuc te. Ta da thong nhat (trong pham vi hop dong toi thieu) rang subscribe khong tu dong deduplicate. Vi vay, ve ly thuyet, ban co the dang ky cung mot doi tuong-handler hai lan, va khi phat su kien no se bi goi hai lan.
fun main() {
val e = Event<String>()
val listener: (String) -> Unit = { println("Got: $it") }
val u1 = e.subscribe(listener)
val u2 = e.subscribe(listener)
e.emit("X")
// Got: X
// Got: X
u1()
e.emit("Y")
// Got: Y (van con 1 dang ky)
u2()
e.emit("Z")
// (im lang)
}
Hanh vi nay hop ly neu ta nghi “day la danh sach cac dang ky”, khong phai “tap hop cac handler duy nhat”. Dieu quan trong la coi no nhu mot phan cua hop dong va khong bat ngo. Neu sau nay ban can chinh sach khac (vi du cam trung lap), do se la mot quyet dinh thiet ke rieng, khong phai “sua nhe cho vui”.
7. Callable references :: va huy dang ky
Kotlin cho phep truyen khong chi lambda, ma con tham chieu ham thong qua ::. Day cung la mot gia tri ham, chi la no duoc lay tu mot ham san co thay vi tu literal lambda.
Vi du:
fun onExpenseAdded(e: Expense) {
println("HANDLER: ${e.title}") // HANDLER: Coffee
}
fun main() {
val service = ExpenseService()
val handler: (Expense) -> Unit = ::onExpenseAdded
val unsub = service.expenseAdded.subscribe(handler)
service.addExpense("Coffee", 250)
unsub()
}
O day, ky luat van giong het: neu ban can huy dang ky ve sau “theo tham chieu”, hay luu no vao bien (handler). Neu ban viet subscribe(::onExpenseAdded) o nhieu noi va mong doi “do la cung mot tham chieu”, ban co the roi vao tinh trang muon phan; an toan hon la giu tham chieu duoi dang gia tri va dung phu thuoc vao may man.
9. Loi thuong gap
Loi so 1: co gang huy dang ky bang mot lambda “giong y het”.
Mot mong muon rat tu nhien: “minh da viet { println(...) }, vay neu viet lai thi no phai la cung mot cai”. Khong: do la mot doi tuong ham moi, nen remove se khong tim thay. Neu ban thiet ke API su kien, token huy dang ky la cach tot nhat de khong bat nguoi dung phai nho ve nhan dang theo tham chieu cua lambda.
Loi so 2: khong luu token huy dang ky, roi sau do muon “tat bang cach nao do”.
Token chi hoat dong neu ban giu no o dau do. Neu ban dang ky o mot noi, muon huy o noi khac, nhung token bi mat, ban se lai quay ve “tim lambda”. Thuc hanh don gian: neu dang ky la tam thoi, token nen duoc luu gan noi quan ly vong doi cua dang ky do.
Loi so 3: tao dang ky moi moi lan bat tinh nang va nhan duoc trung lap.
Kich ban “bat audit” → “bat audit lan nua” can du doan duoc. Neu moi enable() tao ra mot dang ky moi, output se bi lap, va ban se nghi do la “poltergeist”. Thuong cach chua la luu token trong var unsubscribe, va khi bat lai thi khong dang ky them cho den khi da tat dang ky cu.
Loi so 4: mong doi huy dang ky anh huong toi dot phat dang dien ra “ngay lap tuc”.
De hieu nham la muon: neu handler tu huy dang ky, thi no lap tuc bien mat khoi vong lap hien tai. Trong trien khai toi thieu cua Event, hanh vi nay khong duoc dam bao va phu thuoc vao cach viet emit. Hien tai ta chi co hop dong: “sau khi huy dang ky, cac lan emit trong tuong lai khong duoc goi handler nua”, con tinh on dinh khi thay doi dang ky trong luc dang phat se duoc ban sau.
Loi so 5: lam listeners thanh public “cho de huy dang ky”.
Day la con duong nhanh nhat de code ben ngoai tu y xoa, them va tron listeners theo y muon, va Event bien thanh mot dong hon don. Hay giu collection listeners o muc private, va chi dua ra ngoai cac thao tac ro rang (subscribe, emit va token huy dang ky).
GO TO FULL VERSION