Android 設計模式學習(二)觀察者模式應用實例

前言

最近在遇到了 Android 的開發中常用到的設計模式之觀察者模式,觀察者模式,所謂的模式就是一種設計思想,可以按照某種模式,寫出更合理,簡單,有效的代碼。可以用在 Android 開發中,也可以用在 Java,C#等等開發中,就類似單例模式,代理模式,模版等等。

觀察者模式在實際項目中使用的也是非常頻繁的,它最常用的地方是 GUI 系統、訂閱——發佈系統等。因爲這個模式的一個重要作用就是解耦,使得它們之間的依賴性更小,甚至做到毫無依賴。以 GUI 系統來說,應用的 UI 具有易變性,尤其是前期隨着業務的改變或者產品的需求修改,應用界面也經常性變化,但是業務邏輯基本變化不大,此時,GUI 系統需要一套機制來應對這種情況,使得 UI 層與具體的業務邏輯解耦,觀察者模式此時就派上用場了。

一、觀察者模式概念

1、定義

定義對象間的一種一對多的依賴關係,當一個對象的狀態發送改變時,所以依賴於它的對象都得到通知並被自動更新。

2、介紹

  • 觀察者模式屬於行爲型模式。
  • 觀察者模式又被稱作發佈/訂閱模式。
  • 觀察者模式主要用來解耦,將被觀察者和觀察者解耦,讓他們之間沒有沒有依賴或者依賴關係很小。

3、使用場景

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

4、舉例說明

  • 例一:生活中,我們一羣人圍着鍋喫飯,飯好了,我們就開喫。(觀察者:人們,被觀察者:飯)
  • 例二:Android 中,最常見的點擊事件,通過設置控件的 OnClickListener 並傳入一個 OnClickListener 的實現類來回調點擊事件。(觀察者:OnClickListener,被觀察者:控件)
  • 例三:Android 中,我們從 A 頁面–>B 頁面–>C 頁面–>D 頁面–>F 頁面…. 我們想把 A 頁面信息傳遞給最後一個頁面,如果通過頁面傳遞那麼很繁瑣,我們直接可以在需要的頁面去訂閱 A 頁面的事件,當 A 頁面刷行數據,其他訂閱了 A 頁面事件的就可以直接接受數據。(相當於少了中間商賺差價,是不爽了很多,而且效率還比較高)
  • 例四:Android 中,我們常用的 recyclerView,listView 刷行數據時調用 notifyDataSetChanged()來更新 ui,想知道具體原因,那麼請仔細往下看完這篇文章。
  • 例五:Android 中,我們通常發送一個廣播,凡是註冊了該廣播的都可以接收到該廣播,這也是 Android 中典型的觀察者模式。

二、觀察者模式 UML 類圖

角色介紹:

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

    1. addObserver 方法可以添加觀察者對象,可以理解爲觀察者把自己註冊到了被觀察者這裏,只有註冊了的觀察者,才能接到被觀察者的通知。
    2. deleteObserver 方法是將觀察者移除,被移除的觀察者自然就不能再接到通知了。
    3. notifyObserves 方法可以把通知發送給所有的已註冊的觀察者,至於觀察者們後續做什麼事情,被觀察者是完全不關心的。
  • Observer (抽象觀察者):爲所有的具體觀察者定義一個接口,在得到主題通知時更新自己。

  • ConcreteSubject(被觀察者的具體實現):將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。

  • ConcrereObserver(觀察者的具體實現):實現抽象觀察者定義的更新接口,當得到主題更改通知時更新自身的狀態。

三、觀察者模式實現 Kotlin 實現

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

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

該抽象主題定義了一些通用的方法(訂閱、取消訂閱、通知),即具體主題裏面需要實現的。

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

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

被觀察者的具體實現,完成觀察者對其訂閱、取消訂閱以及遍歷通知所有觀察者 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、創建抽象觀察者

定義了所有具體觀察者需要實現的方法,收到電視劇更新的通知

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

4、創建具體觀察者

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

5、模擬實現

以上 4 步基本上已經完成觀察者模式的創建工作,下面模擬 2 個觀察者實現以上功能。

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("五十度飛更新了!!!")
    }
    fun cancel(view: View) {
        teleplay.deleteObserver(person1)
        teleplay.deleteObserver(person2)
    }

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

上述代碼中流程:

  1. 創建了 2 個 Person 對象(即觀察者對象),一個 Teleplay 對象(即被觀察者對象)
  2. 通過點擊事件完成添加訂閱方法:teleplay.addObserver()
  3. 電視劇更新後通過點擊事件調用:teleplay.notifyObserver()方法完成通知
  4. 觀察者收到通知消息:
msg: 張三瘋 , 五十度飛更新了!!!
msg: 趙四史 , 五十度飛更新了!!!
  1. 在不需要監聽時,記得取消訂閱:teleplay.deleteObserver()

到這裏我們便實現了觀察者模式。

四、Android 源碼中觀察者模式

1、notifyDataSetChanged

無論 ListView 還是 RecyclerView 裏,notifyDataSetChanged 方法都是至關重要的,這是最常見的觀察者模式。

當 ListView 的數據發生變化時,我們調用 Adapter 的 notifyDataSetChanged()方法,這個方法又會調用所有觀察者(AdapterDataSetObserver)的 onChanged()方法,onChanged()方法又會調用 requestLayout()方法來重新進行佈局。

2、BroadcastReceiver

BroadcastReceiver 作爲 Android 的四大組件之一,實際上也是一個典型的觀察者模式.通過 sendBroadcast 發送廣播時,只有註冊了相應的 IntentFilter 的 BroadcastReceiver 對象纔會收到這個廣播信息,其 onReceive 方法纔會被調起.

3、EventBus

EventBus 是一個組件間通信框架,開發者在 Activity、Fragment、Service、Thread 之間傳遞消息時可以避免使用複雜的 Intent、Handler 和 BroadCast

4、RxJava

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

五、常見面試題

1、Android 開發中如何利用觀察者模式?

  • 在觀察者模式中,觀察者和被觀察者之間是抽象耦合,保證了訂閱系統的靈活性和可擴展性。在需要 UI 層與業務邏輯解耦的關聯行爲場景或事件多級觸發場景非常實用。
  • 跨進程或者跨 App 的消息交換場景。

2、回調函數和觀察者模式的區別?

  • 觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。觀察者模式完美的將觀察者和被觀察的對象分離開,一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新。
  • 回調函數其實也算是一種觀察者模式的實現方式,回調函數實現的觀察者和被觀察者往往是一對一的依賴關係。

所以最明顯的區別是觀察者模式是一種設計思路,而回調函數式一種具體的實現方式;另一明顯區別是一對多還是多對多的依賴關係方面。

六、總結

觀察者模式就是將觀察者和被觀察者徹底隔離,實現解耦,只依賴於我們定義的抽象。

優點

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

缺點

  • 依賴關係並未完全解除,抽象主題仍然依賴抽象觀察者。
  • 使用觀察者模式時需要考慮一下開發效率和運行效率的問題,程序中包括一個被觀察者、多個觀察者,開發、調試等內容會比較複雜,而且在 Java 中消息的通知一般是順序執行,那麼一個觀察者卡頓,會影響整體的執行效率,在這種情況下,一般會採用異步實現。
  • 可能會引起多餘的數據通知。

觀察者模式看起來很高大上,其實說白了就是一個類維護了另一個類的一個集合,並通過這個集合綁定解綁或調用另一個類的方法,只不過,在設計底層框架時候,利用了多態的特性抽象出了接口和抽象類,以便適用於各種場合。

其實在做終端頁面時候完全用不到,因爲多態只能增加運行時開銷。然而,設置一個龐大系統時候,這種設計模式在面向對象的編程語言,可謂不能不用的手段了。

文中完整DEMO:點擊下載
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章