1.初識觀察者模式
定義對象間的一種一對多的依賴關(guān)系纵寝,當(dāng)一個對象的狀態(tài)發(fā)生改變時论寨,所有依賴于它的對象都得到通知并被自動更新。
- Subject:目標(biāo)對象店雅,通常具有如下功能:
(1)一個目標(biāo)可以被多個觀察者觀察
(2)目標(biāo)提供對觀察者注冊和退訂的維護(hù)
(3)當(dāng)目標(biāo)的狀態(tài)發(fā)生變化時政基,目標(biāo)負(fù)責(zé)通知所有注冊的、有效的觀察者
Observer:定義觀察者的接口闹啦,提供目標(biāo)通知時對應(yīng)的更新方法沮明,這個更新方法進(jìn)行相應(yīng)的業(yè)務(wù)處理,可以在這個方法里面回調(diào)目標(biāo)對象窍奋,以獲取目標(biāo)對象的數(shù)據(jù)荐健。
ConcreteSubject:具體的目標(biāo)實(shí)現(xiàn)對象酱畅,用來維護(hù)目標(biāo)狀態(tài),當(dāng)目標(biāo)對象的狀態(tài)發(fā)生改變時江场,通知所有注冊有效的觀察者纺酸,讓觀察者執(zhí)行相應(yīng)的處理。
ConcreteObserver:觀察者的具體實(shí)現(xiàn)對象址否,用來接收目標(biāo)的通知餐蔬,并進(jìn)行相應(yīng)的后續(xù)處理,比如更新自身的狀態(tài)以保持和目標(biāo)的相應(yīng)狀態(tài)一致佑附。
2.體會觀察者模式
2.1 場景問題——訂閱報紙
訂閱報紙的過程
在整個過程中樊诺,郵局只不過起到一個中轉(zhuǎn)的作用,為了簡單音同,我們?nèi)サ羿]局词爬,讓訂閱者直接和報社交互
訂閱報紙的問題:
在上述過程中,訂閱者在完成訂閱后权均,最關(guān)心的問題就是何時能收到新出的報紙顿膨。幸好在現(xiàn)實(shí)生活中,報紙都是定期出版叽赊,這樣發(fā)放到訂閱者手中也基本上有一個大致的時間范圍恋沃,差不多到時間了,訂閱者就會看看郵箱蛇尚,查收新的報紙芽唇。
要是報紙出版的時間不固定呢?
那訂閱者就麻煩了取劫,如果訂閱者想要第一時間閱讀到新報紙,恐怕只能天天守著郵箱了研侣,這未免也太痛苦了吧谱邪。
繼續(xù)引申一下,用類來描述上述的過程庶诡,描述如下:
訂閱者類向出版者類訂閱報紙惦银,很明顯不會只有一個訂閱者訂閱報紙,訂閱者類可以有很多末誓;當(dāng)出版者類出版新報紙的時候扯俱,多個訂閱者類如何知道呢?還有訂閱者類如何得到新報紙的內(nèi)容呢喇澡?
把上面的問題對比描述一下:
進(jìn)一步抽象描述這個問題:當(dāng)一個對象的狀態(tài)發(fā)生改變的時候迅栅,如何讓依賴于它的所有對象得到通知,并進(jìn)行相應(yīng)的處理呢晴玖?
2.2 使用模式的解決方案
3.理解觀察者模式
3.1 認(rèn)識觀察者模式
3.1.1 目標(biāo)和觀察者之間的關(guān)系
按照模式的定義读存,目標(biāo)和觀察者之間是典型的一對多的關(guān)系为流。
但是要注意,如果觀察者只有一個让簿,也是可以的敬察,這樣就變相實(shí)現(xiàn)了目標(biāo)和觀察者之間一對一的關(guān)系,這也使得在處理一個對象的狀態(tài)變化會影響到另一個對象的時候尔当,也可以考慮使用觀察者模式莲祸。
同樣的,一個觀察者也可以觀察多個目標(biāo)椭迎,如果觀察者為多個目標(biāo)定義的通知更新方法都是update方法的話锐帜,這會帶來麻煩,因?yàn)樾枰邮斩鄠€目標(biāo)的通知侠碧,如果是一個update的方法抹估,那就需要在方法內(nèi)部區(qū)分,到底這個更新的通知來自于哪一個目標(biāo)弄兜,不同的目標(biāo)有不同的后續(xù)操作药蜻。
一般情況下,觀察者應(yīng)該為不同的觀察者目標(biāo)替饿,定義不同的回調(diào)方法语泽,這樣實(shí)現(xiàn)最簡單,不需要在update方法內(nèi)部進(jìn)行區(qū)分视卢。
3.1.2 單向依賴
在觀察者模式中踱卵,觀察者和目標(biāo)是單向依賴的,只有觀察者依賴于目標(biāo)据过,而目標(biāo)是不會依賴于觀察者的惋砂。
它們之間聯(lián)系的主動權(quán)掌握在目標(biāo)手中,只有目標(biāo)知道什么時候需要通知觀察者绳锅,在整個過程中西饵,觀察者始終是被動的,被動的等待目標(biāo)的通知鳞芙,等待目標(biāo)傳值給它眷柔。
對目標(biāo)而言,所有的觀察者都是一樣的原朝,目標(biāo)會一視同仁的對待驯嘱。當(dāng)然也可以通過在目標(biāo)里面進(jìn)行控制,實(shí)現(xiàn)有區(qū)別對待觀察者喳坠,比如某些狀態(tài)變化鞠评,只需要通知部分觀察者,但那是屬于稍微變形的用法了丙笋,不屬于標(biāo)準(zhǔn)的谢澈、原始的觀察者模式了煌贴。
3.1.3 基本的實(shí)現(xiàn)說明
具體的目標(biāo)實(shí)現(xiàn)對象要能維護(hù)觀察者的注冊信息,最簡單的實(shí)現(xiàn)方案就如同前面的例子那樣锥忿,采用一個集合來保存觀察者的注冊信息牛郑。
具體的目標(biāo)實(shí)現(xiàn)對象需要維護(hù)引起通知的狀態(tài),一般情況下是目標(biāo)自身的狀態(tài)敬鬓,變形使用的情況下淹朋,也可以是別的對象的狀態(tài)。
具體的觀察者實(shí)現(xiàn)對象需要能接收目標(biāo)的通知钉答,能夠接收目標(biāo)傳遞的數(shù)據(jù)础芍,或者是能夠主動去獲取目標(biāo)的數(shù)據(jù),并進(jìn)行后續(xù)處理数尿。
如果是一個觀察者觀察多個目標(biāo)仑性,那么在觀察者的更新方法里面,需要去判斷是來自哪一個目標(biāo)的通知右蹦。一種簡單的解決方案就是擴(kuò)展update方法诊杆,比如在方法里面多傳遞一個參數(shù)進(jìn)行區(qū)分等;還有一種更簡單的方法何陆,那就是干脆定義不同的回調(diào)方法晨汹。
3.1.4 命名建議
(1)觀察者模式又被稱為發(fā)布-訂閱模式
(2)目標(biāo)接口的定義,建議在名稱后面跟Subject
(3)觀察者接口的定義贷盲,建議在名稱后面跟Observer
(4)觀察者接口的更新方法淘这,建議名稱為update,當(dāng)然方法的參數(shù)可以根據(jù)需要定義巩剖,參數(shù)個數(shù)不限铝穷、參數(shù)類型不限
3.1.5 觸發(fā)通知的時機(jī)
一般情況下,是在完成了狀態(tài)維護(hù)后觸發(fā)佳魔,因?yàn)橥ㄖ獣鬟f數(shù)據(jù)氧骤,不能夠先通知后改數(shù)據(jù),這很容易出問題吃引,會導(dǎo)致觀察者和目標(biāo)對象的狀態(tài)不一致。
3.1.6 相互觀察
A對象的狀態(tài)變化會引起C對象的聯(lián)動操作刽锤,反過來镊尺,C 對象的狀態(tài)變化也會引起A對象的聯(lián)動操作。對于出現(xiàn)這種狀況并思,要特別小心處理庐氮,因?yàn)榭赡軙霈F(xiàn)死循環(huán)的情況。
3.1.7 觀察者模式的調(diào)用順序示意圖
在使用觀察者模式時宋彼,會很明顯的分成兩個階段弄砍,第一個階段是準(zhǔn)備階段仙畦,也就是維護(hù)目標(biāo)和觀察者關(guān)系的階段,這個階段的調(diào)用順序如圖
接下來就是實(shí)際的運(yùn)行階段了音婶,這個階段的調(diào)用順序如圖
3.1.8 通知的順序
從理論上說慨畸,當(dāng)目標(biāo)對象的狀態(tài)變化后通知所有觀察者的時候,順序是不確定的衣式,因此觀察者實(shí)現(xiàn)的功能寸士,絕對不要依賴于通知的順序,也就是說碴卧,多個觀察者之間的功能是平行的弱卡,相互不應(yīng)該有先后的依賴關(guān)系。
3.2 推模型和拉模型
推模型:目標(biāo)對象主動向觀察者推送目標(biāo)的詳細(xì)信息住册,不管觀察者是否需要婶博,推送的信息通常是目標(biāo)對象的全部或部分?jǐn)?shù)據(jù),相當(dāng)于是在廣播通信荧飞。
拉模型:目標(biāo)對象在通知觀察者的時候凡人,只傳遞少量信息,如果觀察者需要更具體的信息垢箕,由觀察者主動到目標(biāo)對象中獲取划栓,相當(dāng)于是觀察者從目標(biāo)對象中拉數(shù)據(jù)。
一般這種模型的實(shí)現(xiàn)中条获,會把目標(biāo)對象自身通過update方法傳遞給觀察者忠荞,這樣在觀察者需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了
關(guān)于兩種模型的比較:
兩種實(shí)現(xiàn)模型帅掘,在開發(fā)的時候委煤,究竟應(yīng)該使用哪一種,還是應(yīng)該具體問題具體分析修档。這里碧绞,只是把兩種模型進(jìn)行一個簡單的比較。
- 1)推模型是假定目標(biāo)對象知道觀察者需要的數(shù)據(jù)吱窝;而拉模型是目標(biāo)對象不知道觀察者具體需要什么數(shù)據(jù)讥邻,沒有辦法的情況下,干脆把自身傳給觀察者院峡,讓觀察者自己去按需取值兴使。
- 2)推模型可能會使得觀察者對象難以復(fù)用,因?yàn)橛^察者定義的update方法是按需而定義的照激,可能無法兼顧沒有考慮到的使用情況发魄。這就意味著出現(xiàn)新情況的時候,就可能需要提供新的update方法,或者是干脆重新實(shí)現(xiàn)觀察者励幼。
而拉模型就不會造成這樣的情況汰寓,因?yàn)槔P拖拢瑄pdate方法的參數(shù)是目標(biāo)對象本身苹粟,這基本上是目標(biāo)對象能傳遞的最大數(shù)據(jù)集合了有滑,基本上可以適應(yīng)各種情況的需要。
3.3 Java中的觀察者模式
在java.util包里面有一個類Observable六水,它實(shí)現(xiàn)了大部分我們需要的目標(biāo)的功能俺孙;還有一個接口Observer,它里面定義了update的方法掷贾,就是觀察者的接口睛榄。
3.4 Swing中的觀察者模式
Swing中到處都是觀察者模式的身影,比如大家熟悉的事件處理想帅,就是典型的觀察者模式的應(yīng)用场靴。(說明一下:早期的Swing事件處理用的是職責(zé)鏈)
Swing組件是被觀察的目標(biāo),而每個實(shí)現(xiàn)監(jiān)聽器的類就是觀察者港准,監(jiān)聽器的
接口就是觀察者的接口旨剥,在調(diào)用addXXXListener方法的時候就相當(dāng)于注冊觀察者。
當(dāng)組件被點(diǎn)擊浅缸,狀態(tài)發(fā)生改變的時候轨帜,就會產(chǎn)生相應(yīng)的通知,會調(diào)用注冊的觀察者的方法衩椒,就是我們所實(shí)現(xiàn)的監(jiān)聽器的方法蚌父。
從這里還可以學(xué)一招:如何處理一個觀察者觀察多個目標(biāo)對象
3.5 觀察者模式的優(yōu)缺點(diǎn)
- 觀察者模式實(shí)現(xiàn)了觀察者和目標(biāo)之間的抽象耦合
- 觀察者模式實(shí)現(xiàn)了動態(tài)聯(lián)動
- 觀察者模式支持廣播通信
- 觀察者模式可能會引起無謂的操作
4.思考觀察者模式
4.1 觀察者模式的本質(zhì)
觸發(fā)聯(lián)動
4.2 何時選用
- 1)當(dāng)一個抽象模型有兩個方面,其中一個方面的操作依賴于另一個方面的狀態(tài)變化毛萌,那么就可以選用觀察者模式苟弛。
- 2)如果在更改一個對象的時候,需要同時連帶改變其它的對象阁将,而且不知道究竟應(yīng)該有多少對象需要被連帶改變膏秫,這種情況可以選用觀察者模式,被更改的那一個對象很明顯就相當(dāng)于是目標(biāo)對象做盅,而需要連帶修改的多個其它對象缤削,就作為多個觀察者對象了。
- 3)當(dāng)一個對象必須通知其它的對象吹榴,但是你又希望這個對象和其它被它通知的對象是松散耦合的僻他,也就是說這個對象其實(shí)不想知道具體被通知的對象,這種情況可以選用觀察者模式腊尚,這個對象就相當(dāng)于是目標(biāo)對象,而被它通知的對象就是觀察者對象了满哪。
4.3 簡單變形使用示例(區(qū)別對待觀察者)
1.范例需求
這是一個實(shí)際系統(tǒng)的簡化需求:在一個水質(zhì)監(jiān)測系統(tǒng)中有這樣一個功能婿斥,當(dāng)水中的雜質(zhì)為正常的時候劝篷,只是通知監(jiān)測人員做記錄;當(dāng)為輕度污染的時候民宿,除了通知監(jiān)測人員做記錄外娇妓,還要通知預(yù)警人員,判斷是否需要預(yù)警活鹰;當(dāng)為中度或者高度污染的時候哈恰,除了通知監(jiān)測人員做記錄外,還要通知預(yù)警人員志群,判斷是否需要預(yù)警着绷,同時還要通知監(jiān)測部門領(lǐng)導(dǎo)做相應(yīng)的處理。
2.解決思路和范例代碼
分析上述需求就會發(fā)現(xiàn)锌云,對于水質(zhì)污染這件事情荠医,有可能會涉及到監(jiān)測員、預(yù)警人員桑涎、監(jiān)測部門領(lǐng)導(dǎo)彬向,根據(jù)不同的水質(zhì)污染情況涉及到不同的人員,也就是說攻冷,監(jiān)測員娃胆、預(yù)警人員、監(jiān)測部門領(lǐng)導(dǎo)他們?nèi)呤瞧叫械牡嚷氊?zé)都是處理水質(zhì)污染里烦,但是處理的范圍不一樣。
因此很容易套用上觀察者模式涉兽,如果把水質(zhì)污染的記錄當(dāng)作被觀察的目標(biāo)的話招驴,那么監(jiān)測員、預(yù)警人員和監(jiān)測部門領(lǐng)導(dǎo)就都是觀察者了枷畏。
前面學(xué)過的觀察者模式别厘,當(dāng)目標(biāo)通知觀察者的時候是全部都通知,但是現(xiàn)在這個需求是不同的情況來讓不同的人處理拥诡,怎么辦呢触趴?
解決的方式通常有兩種:
一種是目標(biāo)可以通知,但是觀察者不做任何操作渴肉;
另外一種是在目標(biāo)里面進(jìn)行判斷冗懦,干脆就不通知了。
兩種實(shí)現(xiàn)方式各有千秋仇祭,這里選擇后面一種方式來示例披蕉,這種方式能夠統(tǒng)一邏輯控制,并進(jìn)行觀察者的統(tǒng)一分派,有利于業(yè)務(wù)控制和今后的擴(kuò)展没讲。
5.案例:View-Model
大名鼎鼎的 MVC 模式大家一定都聽過眯娱,MVC 分別指 Model、View爬凑、Controller徙缴。在標(biāo)準(zhǔn) MVC 模型中,當(dāng) Model 改變時嘁信,View 視圖會自動改變于样;View 和 Model 之間就是典型的觀察者模式。
誰是觀察者潘靖,誰是被觀察者穿剖?收通知的就是觀察者。在 View 和 Model 中秘豹,顯然 View 收通知的那一方携御,那么 View 就是觀察者。
在 Web 應(yīng)用中既绕,由于 View 是在瀏覽器端展示啄刹,而 Model 是在服務(wù)端,因此不好體現(xiàn)觀察者模式凄贩,但是在桌面應(yīng)用中體現(xiàn)得淋漓盡致誓军。
另外:MVVM 是雙向綁定,View 改變疲扎,Model 也自動更新昵时。其實(shí)原理很簡單,雙方互為觀察者即可椒丧。