一、前言
- 觀察者模式
- 作用:定義了一個一對多的依賴關(guān)系五垮,讓一個或多個觀察者對象監(jiān)聽一個主題對象乍惊。這樣一來,當(dāng)被觀察者狀態(tài)發(fā)生改變時放仗,需要通知相應(yīng)的觀察者润绎,使這些觀察者對象能夠自動更新。
- 核心操作:
- 觀察者(訂閱者)添加或刪除對 被觀察者(主題)的狀態(tài)監(jiān)聽
- 被觀察者(主題)狀態(tài)改變時诞挨,將事件通知給所有觀察者莉撇,觀察者執(zhí)行響應(yīng)邏輯
二、使用觀察者模式
- 例子:監(jiān)聽股票價格變動
- 重點:使用 Java API 或 自定義實現(xiàn) 觀察者模式
1惶傻、使用 Java API 實現(xiàn)觀察者模式
Java 標(biāo)準(zhǔn)庫中提供了通用觀察者模式的 API棍郎,分別是:
-
java.util.Observable
:被觀察者(主題)-
setChanged()
:標(biāo)記狀態(tài)更新 -
addObserver()
:添加觀察者 -
deleteObserver()
:刪除觀察者 -
countObservers()
:獲取觀察者數(shù)量 -
notifyObservers()
:通知所有觀察者 -
notifyObservers(Object arg)
:通知所有觀察者(攜帶參數(shù) arg)
-
-
java.util.Observer
:觀察者(訂閱者)
利用 Java API,可以實現(xiàn)監(jiān)聽股票價格變動這個功能:
import java.util.Observable
import java.util.Observer
/**
* 被觀察者(主題)
*
* @author GitLqr
*/
class StockSubject : Observable() {
fun changeStockPrice(price: Int) {
this.setChanged() // 標(biāo)識狀態(tài)更新
this.notifyObservers(price) // 通知所有觀察者當(dāng)前股票價格
}
}
/**
* 觀察者(訂閱者)
*
* @author GitLqr
*/
class StockDisplay(val name: String) : Observer {
override fun update(o: Observable?, price: Any?) {
println("$name receive stock price : $price") // 注意 price 的類型是 Any?
}
}
// 使用
val subject = StockSubject()
subject.addObserver(StockDisplay("observer 1"))
subject.addObserver(StockDisplay("observer 2"))
subject.changeStockPrice(200)
// 輸出
// observer 2 receive stock price : 200
// observer 1 receive stock price : 200
復(fù)制代碼
注意:在主題中通過
notifyObservers()
方法通知訂閱者之前银室,需要先調(diào)用setChanged()
標(biāo)識狀態(tài)更新涂佃,才能正常通知給訂閱者,這是使用 Java API 實現(xiàn)觀察者模式時需要注意的一點蜈敢。
Java 提供的 API 已經(jīng)涵蓋了觀察者模式的完整實現(xiàn)辜荠,所以我們在使用的時候,只需要關(guān)注業(yè)務(wù)本身扶认,而不用自己去做模式的具體實現(xiàn)侨拦,但是呢殊橙,Java 提供的 API 是一種通用實現(xiàn)辐宾,從上面的例子中可以注意到狱从,StockDisplay.update(o: Observable?, price: Any?)
中的 price
參數(shù)類型是 Any?
,這就會有以下幾個問題:
- 參數(shù)判斷:因為參數(shù)類型是
Any?
叠纹,所以開發(fā)中不得不對 參數(shù)是否為空 以及 參數(shù)的實際類型 做判斷季研。 - 通知入口單一:實際業(yè)務(wù)需求會更加復(fù)雜,而
java.util.Observer
只有唯一一個通知入口update(o: Observable?, arg: Any?)
誉察,所以我們不得不在該方法中分離響應(yīng)邏輯与涡,比如股票價格升降,這會讓代碼顯得臃腫持偏。
2驼卖、自定義實現(xiàn)觀察者模式
雖然 Java 提供了現(xiàn)成的觀察者模式 API,但是實際開發(fā)中鸿秆,我們通常還是會自定義實現(xiàn)觀察者模式酌畜,以便更好的控制代碼結(jié)構(gòu):
/**
* 回調(diào)接口(解耦業(yè)務(wù)通知入口)
*
* @author GitLqr
*/
interface StockUpdateListener {
fun onRise(price: Int)
fun onFall(price: Int)
}
/**
* 被觀察者(主題)
*
* @author GitLqr
*/
class StockSubject {
val listeners = mutableSetOf<StockUpdateListener>()
var price: Int = 0
fun subscribe(observer: StockUpdateListener) {
listeners.add(observer)
}
fun unsubscribe(observer: StockUpdateListener) {
listeners.remove(observer)
}
fun changeStockPrice(price: Int) {
val isRise = price > this.price
listeners.forEach { if (isRise) it.onRise(price) else it.onFall(price) }
this.price = price
}
}
/**
* 觀察者(訂閱者)
*
* @author GitLqr
*/
class StockDisplay : StockUpdateListener {
override fun onRise(price: Int) {
println("The latest stock price has rise to $price")
}
override fun onFall(price: Int) {
println("The latest stock price has fell to $price")
}
}
// 使用
val subject = StockSubject()
subject.subscribe(StockDisplay())
subject.changeStockPrice(200) // The latest stock price has rise to 200
復(fù)制代碼
可見,自定義實現(xiàn)觀察者模式卿叽,可以讓代碼結(jié)構(gòu)變得更加簡單直觀桥胞。
三、改良觀察者模式
- 例子:監(jiān)聽股票價格變動
- 重點:委托屬性
Delegates.observable()
Kotlin 標(biāo)準(zhǔn)庫引入了可被觀察的委托屬性考婴,可通過 xxx by Delegates.observable()
的方式贩虾,用來監(jiān)聽 xxx
屬性的改變,于是可以用來改良上面的自定義觀察者模式:
import kotlin.properties.Delegates
/**
* 觀察者模式改良:使用委托屬性監(jiān)聽值變化后通知
*
* @author GitLqr
*/
class StockSubject {
val listeners = mutableSetOf<StockUpdateListener>()
var price: Int by Delegates.observable(0) { prop, old, new ->
val isRise = new > old
listeners.forEach { if (isRise) it.onRise(price) else it.onFall(price) }
}
fun subscribe(observer: StockUpdateListener) {
listeners.add(observer)
}
fun unsubscribe(observer: StockUpdateListener) {
listeners.remove(observer)
}
// fun changeStockPrice(price: Int) { ... }
}
// 使用
val subject = StockSubject()
subject.subscribe(StockDisplay())
subject.price = 250 // The latest stock price has rise to 200
復(fù)制代碼
使用 Delegates.observable()
之后沥阱,StockSubject
相比之前減少了一個 changeStockPrice()
方法缎罢。使用上,一旦對 price
屬性賦值考杉,就可以觸發(fā)通知屁使,顯然,這對使用者更加友好了(直觀奔则,少記一個方法)蛮寂。
四、補充
前面說到易茬,Kotlin 標(biāo)準(zhǔn)庫引入可被觀察的委托屬性酬蹋,除了 Delegates.observable()
之外,還有 Delegates.vetoable()
也很實用抽莱,當(dāng)我們不希望被監(jiān)控的屬性被隨意修改時范抓,就可以用它來否決屬性賦值:
import kotlin.properties.Delegates
var value: Int by Delegates.vetoable(0) { prop, old, new ->
// 新值大于0時,才給屬性賦值
new > 0
}
// 使用
value = 1
println(value) // 1
value = -1
println(value) // 1(沒能賦值成功)
復(fù)制代碼