Android 設(shè)計模式學習之觀察者模式應(yīng)用實例

前言


最近在遇到了 Android 的開發(fā)中常用到的設(shè)計模式之觀察者模式,觀察者模式澄港,所謂的模式就是一種設(shè)計思想椒涯,可以按照某種模式,寫出更合理回梧,簡單废岂,有效的代碼∮猓可以用在 Android 開發(fā)中湖苞,也可以用在 Java,C#等等開發(fā)中详囤,就類似單例模式财骨,代理模式,模版等等藏姐。

觀察者模式在實際項目中使用的也是非常頻繁的隆箩,它最常用的地方是 GUI 系統(tǒng)、訂閱——發(fā)布系統(tǒng)等羔杨。因為這個模式的一個重要作用就是解耦捌臊,使得它們之間的依賴性更小,甚至做到毫無依賴兜材。以 GUI 系統(tǒng)來說理澎,應(yīng)用的 UI 具有易變性逞力,尤其是前期隨著業(yè)務(wù)的改變或者產(chǎn)品的需求修改,應(yīng)用界面也經(jīng)常性變化糠爬,但是業(yè)務(wù)邏輯基本變化不大寇荧,此時,GUI 系統(tǒng)需要一套機制來應(yīng)對這種情況执隧,使得 UI 層與具體的業(yè)務(wù)邏輯解耦揩抡,觀察者模式此時就派上用場了。

一殴玛、觀察者模式概念

1捅膘、定義

定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)送改變時滚粟,所以依賴于它的對象都得到通知并被自動更新寻仗。

2、介紹

  • 觀察者模式屬于行為型模式凡壤。
  • 觀察者模式又被稱作發(fā)布/訂閱模式署尤。
  • 觀察者模式主要用來解耦,將被觀察者和觀察者解耦亚侠,讓他們之間沒有沒有依賴或者依賴關(guān)系很小曹体。

3、使用場景

  • 當一個對象的改變需要通知其它對象改變時硝烂,而且它不知道具體有多少個對象有待改變時箕别。
  • 當一個對象必須通知其它對象,而它又不能假定其它對象是誰
  • 跨系統(tǒng)的消息交換場景滞谢,如消息隊列串稀、事件總線的處理機制。

4狮杨、舉例說明

  • 例一:生活中母截,我們一群人圍著鍋吃飯,飯好了橄教,我們就開吃清寇。(觀察者:人們,被觀察者:飯)
  • 例二:Android 中护蝶,最常見的點擊事件华烟,通過設(shè)置控件的 OnClickListener 并傳入一個 OnClickListener 的實現(xiàn)類來回調(diào)點擊事件。(觀察者:OnClickListener持灰,被觀察者:控件)
  • 例三:Android 中垦江,我們從 A 頁面–>B 頁面–>C 頁面–>D 頁面–>F 頁面…. 我們想把 A 頁面信息傳遞給最后一個頁面,如果通過頁面?zhèn)鬟f那么很繁瑣,我們直接可以在需要的頁面去訂閱 A 頁面的事件比吭,當 A 頁面刷行數(shù)據(jù),其他訂閱了 A 頁面事件的就可以直接接受數(shù)據(jù)姨涡。(相當于少了中間商賺差價衩藤,是不爽了很多,而且效率還比較高)
  • 例四:Android 中涛漂,我們常用的 recyclerView赏表,listView 刷行數(shù)據(jù)時調(diào)用 notifyDataSetChanged()來更新 ui,想知道具體原因匈仗,那么請仔細往下看完這篇文章瓢剿。
  • 例五:Android 中,我們通常發(fā)送一個廣播悠轩,凡是注冊了該廣播的都可以接收到該廣播间狂,這也是 Android 中典型的觀察者模式。

二火架、觀察者模式 UML 類圖

image

角色介紹:

  • Subject(被觀察者):把所有觀察者對象的引用保存到一個集合里鉴象,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口何鸡,可以增加和刪除觀察者對象纺弊,主要包含三個方法:

    1. addObserver 方法可以添加觀察者對象,可以理解為觀察者把自己注冊到了被觀察者這里骡男,只有注冊了的觀察者淆游,才能接到被觀察者的通知。
    2. deleteObserver 方法是將觀察者移除隔盛,被移除的觀察者自然就不能再接到通知了犹菱。
    3. notifyObserves 方法可以把通知發(fā)送給所有的已注冊的觀察者,至于觀察者們后續(xù)做什么事情骚亿,被觀察者是完全不關(guān)心的已亥。
  • Observer (抽象觀察者):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己来屠。

  • ConcreteSubject(被觀察者的具體實現(xiàn)):將有關(guān)狀態(tài)存入具體觀察者對象虑椎;在具體主題內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知俱笛。

  • ConcrereObserver(觀察者的具體實現(xiàn)):實現(xiàn)抽象觀察者定義的更新接口捆姜,當?shù)玫街黝}更改通知時更新自身的狀態(tài)。

三迎膜、觀察者模式實現(xiàn) Kotlin 實現(xiàn)

下面以日常生活中追劇案例實現(xiàn)觀察者模式泥技,如果電視劇更新,則會通知所有訂閱者磕仅。(典型的一對多關(guān)系)

1珊豹、定義一個抽象主題,抽象被觀察者

該抽象主題定義了一些通用的方法(訂閱簸呈、取消訂閱、通知)店茶,即具體主題里面需要實現(xiàn)的蜕便。

interface Observable {
    /**
     * 添加觀察者
     */
    fun addObserver(observer: Observer)
    /**
     * 移除觀察者
     */
    fun deleteObserver(observer: Observer)
    /**
     * 通知觀察者
     */
    fun notifyObserver(msg: String)
}

2、定義具體主題(電視劮坊谩)具體的被觀察者

被觀察者的具體實現(xiàn)轿腺,完成觀察者對其訂閱、取消訂閱以及遍歷通知所有觀察者 msg 方法丛楚。

class Teleplay : Observable {
    // 保存觀察者對象
    var list: MutableList<Observer> = ArrayList()
    /**
     * 添加訂閱
     */
    override fun addObserver(observer: Observer) {
        if(!list.contains(observer)){
            list.add(observer)
        }
    }
    /**
     * 取消訂閱
     */
    override fun deleteObserver(observer: Observer) {
        list.remove(observer)
    }

    /**
     * 通知觀察者族壳,遍歷通知所有觀察者對象
     */
    override fun notifyObserver(msg: String) {
        list.forEach {
            it.action(msg);
        }
    }
}

3、創(chuàng)建抽象觀察者

定義了所有具體觀察者需要實現(xiàn)的方法趣些,收到電視劇更新的通知

interface Observer {
    /**
     * 更新內(nèi)容
     */
    fun action(msg:String);
}

4仿荆、創(chuàng)建具體觀察者

class Person(private var name: String) : Observer {
    /**
     * 接收被觀察者發(fā)送的通知
     */
    override fun action(msg: String) {
        Log.e("msg","$name , $msg")
    }
}

5、模擬實現(xiàn)

以上 4 步基本上已經(jīng)完成觀察者模式的創(chuàng)建工作喧务,下面模擬 2 個觀察者實現(xiàn)以上功能赖歌。

class MainActivity : AppCompatActivity(){

    private val teleplay:Teleplay by lazy {
        Teleplay()
    }
    private val person1:Person by lazy {
        Person("張三瘋")
    }
    private val person2:Person by lazy {
        Person("趙四史")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    fun subscribe01(view: View) {
        teleplay.addObserver(person1)
    }
    fun subscribe02(view: View) {
        teleplay.addObserver(person2)
    }
    fun upDate(view: View) {
        teleplay.notifyObserver("五十度飛更新了!9睢庐冯!")
    }
    fun cancel(view: View) {
        teleplay.deleteObserver(person1)
        teleplay.deleteObserver(person2)
    }

    override fun onDestroy() {
        super.onDestroy()
        teleplay.deleteObserver(person1)
        teleplay.deleteObserver(person2)
    }
}

上述代碼中流程:

  1. 創(chuàng)建了 2 個 Person 對象(即觀察者對象),一個 Teleplay 對象(即被觀察者對象)
  2. 通過點擊事件完成添加訂閱方法:teleplay.addObserver()
  3. 電視劇更新后通過點擊事件調(diào)用:teleplay.notifyObserver()方法完成通知
  4. 觀察者收到通知消息:
msg: 張三瘋 , 五十度飛更新了?泊U垢浮!
msg: 趙四史 , 五十度飛更新了A崦痢F苘浴!
  1. 在不需要監(jiān)聽時孵延,記得取消訂閱:teleplay.deleteObserver()

到這里我們便實現(xiàn)了觀察者模式吕漂。

四、Android 源碼中觀察者模式

1尘应、notifyDataSetChanged

無論 ListView 還是 RecyclerView 里惶凝,notifyDataSetChanged 方法都是至關(guān)重要的,這是最常見的觀察者模式犬钢。

當 ListView 的數(shù)據(jù)發(fā)生變化時苍鲜,我們調(diào)用 Adapter 的 notifyDataSetChanged()方法,這個方法又會調(diào)用所有觀察者(AdapterDataSetObserver)的 onChanged()方法玷犹,onChanged()方法又會調(diào)用 requestLayout()方法來重新進行布局混滔。

2、BroadcastReceiver

BroadcastReceiver 作為 Android 的四大組件之一,實際上也是一個典型的觀察者模式.通過 sendBroadcast 發(fā)送廣播時,只有注冊了相應(yīng)的 IntentFilter 的 BroadcastReceiver 對象才會收到這個廣播信息,其 onReceive 方法才會被調(diào)起.

3、EventBus

EventBus 是一個組件間通信框架坯屿,開發(fā)者在 Activity油湖、Fragment、Service领跛、Thread 之間傳遞消息時可以避免使用復雜的 Intent肺魁、Handler 和 BroadCast

4、RxJava

RxJava 作為同樣基于觀察者模式的組件間通信框架隔节,要比 EventBus 的應(yīng)用更廣泛。尤其它針對 Android 的擴展——RxAndroid 完全可以替代 AsycTask 來完成各種異步操作寂呛,而且還有 BindActivity 和 BindFragment 方法來避免異步操作時的 Activity 和 Fragment 的生命周期問題怎诫。

五、常見面試題

1贷痪、Android 開發(fā)中如何利用觀察者模式幻妓?

  • 在觀察者模式中,觀察者和被觀察者之間是抽象耦合劫拢,保證了訂閱系統(tǒng)的靈活性和可擴展性肉津。在需要 UI 層與業(yè)務(wù)邏輯解耦的關(guān)聯(lián)行為場景或事件多級觸發(fā)場景非常實用。
  • 跨進程或者跨 App 的消息交換場景舱沧。

2妹沙、回調(diào)函數(shù)和觀察者模式的區(qū)別?

  • 觀察者模式定義了一種一對多的依賴關(guān)系熟吏,讓多個觀察者對象同時監(jiān)聽某一個主題對象距糖。觀察者模式完美的將觀察者和被觀察的對象分離開,一個對象的狀態(tài)發(fā)生變化時牵寺,所有依賴于它的對象都得到通知并自動刷新悍引。
  • 回調(diào)函數(shù)其實也算是一種觀察者模式的實現(xiàn)方式,回調(diào)函數(shù)實現(xiàn)的觀察者和被觀察者往往是一對一的依賴關(guān)系帽氓。

所以最明顯的區(qū)別是觀察者模式是一種設(shè)計思路趣斤,而回調(diào)函數(shù)式一種具體的實現(xiàn)方式;另一明顯區(qū)別是一對多還是多對多的依賴關(guān)系方面黎休。

六浓领、總結(jié)

觀察者模式就是將觀察者和被觀察者徹底隔離,實現(xiàn)解耦奋渔,只依賴于我們定義的抽象镊逝。

優(yōu)點

  • 解除觀察者與主題之間的耦合。讓耦合的雙方都依賴于抽象嫉鲸,而不是依賴具體撑蒜。從而使得各自的變化都不會影響另一邊的變化。
  • 易于擴展,對同一主題新增觀察者時無需修改原有代碼座菠。

缺點

  • 依賴關(guān)系并未完全解除溯革,抽象主題仍然依賴抽象觀察者陆赋。
  • 使用觀察者模式時需要考慮一下開發(fā)效率和運行效率的問題,程序中包括一個被觀察者、多個觀察者揍移,開發(fā)、調(diào)試等內(nèi)容會比較復雜衷蜓,而且在 Java 中消息的通知一般是順序執(zhí)行蔼夜,那么一個觀察者卡頓,會影響整體的執(zhí)行效率品嚣,在這種情況下炕倘,一般會采用異步實現(xiàn)。
  • 可能會引起多余的數(shù)據(jù)通知翰撑。

觀察者模式看起來很高大上罩旋,其實說白了就是一個類維護了另一個類的一個集合,并通過這個集合綁定解綁或調(diào)用另一個類的方法眶诈,只不過涨醋,在設(shè)計底層框架時候,利用了多態(tài)的特性抽象出了接口和抽象類逝撬,以便適用于各種場合浴骂。

其實在做終端頁面時候完全用不到,因為多態(tài)只能增加運行時開銷球拦。然而靠闭,設(shè)置一個龐大系統(tǒng)時候,這種設(shè)計模式在面向?qū)ο蟮木幊陶Z言坎炼,可謂不能不用的手段了愧膀。


111111111111111.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谣光,隨后出現(xiàn)的幾起案子檩淋,更是在濱河造成了極大的恐慌,老刑警劉巖萄金,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟀悦,死亡現(xiàn)場離奇詭異,居然都是意外死亡氧敢,警方通過查閱死者的電腦和手機日戈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孙乖,“玉大人浙炼,你說我怎么就攤上這事份氧。” “怎么了弯屈?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵蜗帜,是天一觀的道長。 經(jīng)常有香客問我资厉,道長厅缺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任宴偿,我火速辦了婚禮湘捎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窄刘。我一直安慰自己消痛,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布都哭。 她就那樣靜靜地躺著,像睡著了一般逞带。 火紅的嫁衣襯著肌膚如雪欺矫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天展氓,我揣著相機與錄音穆趴,去河邊找鬼。 笑死遇汞,一個胖子當著我的面吹牛未妹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播空入,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼络它,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了歪赢?” 一聲冷哼從身側(cè)響起化戳,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埋凯,沒想到半個月后点楼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡白对,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年掠廓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甩恼。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蟀瞧,死狀恐怖沉颂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黄橘,我是刑警寧澤兆览,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站塞关,受9級特大地震影響抬探,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帆赢,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一小压、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椰于,春花似錦怠益、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偏陪,卻和暖如春抢呆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笛谦。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工抱虐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饥脑。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓恳邀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灶轰。 傳聞我的和親對象是個殘疾皇子谣沸,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345