Kotlin - 改良觀察者模式

一、前言

  • 觀察者模式
    • 作用:定義了一個一對多的依賴關(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ù)制代碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末食铐,一起剝皮案震驚了整個濱河市匕垫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虐呻,老刑警劉巖象泵,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寞秃,死亡現(xiàn)場離奇詭異,居然都是意外死亡偶惠,警方通過查閱死者的電腦和手機春寿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忽孽,“玉大人绑改,你說我怎么就攤上這事⌒忠唬” “怎么了厘线?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長出革。 經(jīng)常有香客問我皆的,道長,這世上最難降的妖魔是什么蹋盆? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任费薄,我火速辦了婚禮,結(jié)果婚禮上栖雾,老公的妹妹穿的比我還像新娘楞抡。我一直安慰自己,他們只是感情好析藕,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布召廷。 她就那樣靜靜地躺著,像睡著了一般账胧。 火紅的嫁衣襯著肌膚如雪竞慢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天治泥,我揣著相機與錄音筹煮,去河邊找鬼。 笑死居夹,一個胖子當(dāng)著我的面吹牛败潦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播准脂,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼劫扒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狸膏?” 一聲冷哼從身側(cè)響起沟饥,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贤旷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體广料,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年遮晚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拦止。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡县遣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汹族,到底是詐尸還是另有隱情萧求,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布顶瞒,位于F島的核電站夸政,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榴徐。R本人自食惡果不足惜守问,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坑资。 院中可真熱鬧耗帕,春花似錦、人聲如沸袱贮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攒巍。三九已至嗽仪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柒莉,已是汗流浹背闻坚。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兢孝,地道東北人鲤氢。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像西潘,于是被迫代替她去往敵國和親卷玉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android開發(fā)中的基于觀察者模式實現(xiàn)的設(shè)計還是很多的喷市,比如rxjava相种、LiveData...常見的按鈕點擊事...
    魁地奇閱讀 214評論 0 0
  • koitlin 行為模式---觀察者模式 簡單來說,需要滿足的兩件事: 訂閱者,添加或者刪除 觀察者的監(jiān)聽 發(fā)布者...
    zcwfeng閱讀 611評論 0 3
  • 在現(xiàn)實世界中寝并,許多對象并不是獨立存在的箫措,其中一個對象的行為發(fā)生改變可能會導(dǎo)致一個或者多個其他對象的行為也發(fā)生改變。...
    Zal哥哥閱讀 389評論 0 0
  • 一、需求 外出游玩之前镀岛,必不可少的準(zhǔn)備就是查看天氣預(yù)報弦牡,現(xiàn)在很多天氣預(yù)報APP的數(shù)據(jù)都是實時更新的,那么這...
    Javar閱讀 379評論 0 0
  • 概述 有時被稱作發(fā)布/訂閱模式漂羊,觀察者模式定義了一種一對多的依賴關(guān)系驾锰,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個...
    小狐憨憨閱讀 488評論 0 0