前言
最近在遇到了 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(被觀察者):把所有觀察者對象的引用保存到一個集合裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,主要包含三個方法:
- addObserver 方法可以添加觀察者對象,可以理解爲觀察者把自己註冊到了被觀察者這裏,只有註冊了的觀察者,才能接到被觀察者的通知。
- deleteObserver 方法是將觀察者移除,被移除的觀察者自然就不能再接到通知了。
- 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)
}
}
上述代碼中流程:
- 創建了 2 個 Person 對象(即觀察者對象),一個 Teleplay 對象(即被觀察者對象)
- 通過點擊事件完成添加訂閱方法:teleplay.addObserver()
- 電視劇更新後通過點擊事件調用:teleplay.notifyObserver()方法完成通知
- 觀察者收到通知消息:
msg: 張三瘋 , 五十度飛更新了!!!
msg: 趙四史 , 五十度飛更新了!!!
- 在不需要監聽時,記得取消訂閱: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:點擊下載