前言
最近在遇到了 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 類圖
角色介紹:
-
Subject(被觀察者):把所有觀察者對象的引用保存到一個集合里鉴象,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口何鸡,可以增加和刪除觀察者對象纺弊,主要包含三個方法:
- addObserver 方法可以添加觀察者對象,可以理解為觀察者把自己注冊到了被觀察者這里骡男,只有注冊了的觀察者淆游,才能接到被觀察者的通知。
- deleteObserver 方法是將觀察者移除隔盛,被移除的觀察者自然就不能再接到通知了犹菱。
- 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)
}
}
上述代碼中流程:
- 創(chuàng)建了 2 個 Person 對象(即觀察者對象),一個 Teleplay 對象(即被觀察者對象)
- 通過點擊事件完成添加訂閱方法:teleplay.addObserver()
- 電視劇更新后通過點擊事件調(diào)用:teleplay.notifyObserver()方法完成通知
- 觀察者收到通知消息:
msg: 張三瘋 , 五十度飛更新了?泊U垢浮!
msg: 趙四史 , 五十度飛更新了A崦痢F苘浴!
- 在不需要監(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言坎炼,可謂不能不用的手段了愧膀。