設(shè)計(jì)模式 ——— 觀察者模式

OBSERVER(觀察者) ———— 對象行為型模式

意圖

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

功能

Observer模式中的關(guān)鍵對象是目標(biāo)(subject)和觀察者(observer)。一個目標(biāo)可以有任意數(shù)目的依賴它的觀察者。一旦目標(biāo)的狀態(tài)發(fā)生改變,所有的觀察者都得到通知。作為對這個通知的響應(yīng),每個觀察者都將查詢目標(biāo)以使其狀態(tài)與目標(biāo)的狀態(tài)同步。

這種交互也稱為發(fā)布-訂閱(publish-subscribe)。目標(biāo)是通知的發(fā)布者。它發(fā)出通知時并不需要知道誰是它的觀察者。可以有任意數(shù)目的觀察者訂閱并接受通知。

適用性

在以下任一情況下可以使用觀察者模式
① 當(dāng)一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這二者封裝在獨(dú)立的對象中以使它們可以各自獨(dú)立地改變和復(fù)用。
② 當(dāng)對一個對象的改變需要同時改變其它對象,而不知道具體有多少對象有待改變。
③ 當(dāng)一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之,你不希望這些對象是緊密耦合的。

結(jié)構(gòu)

觀察者模式結(jié)構(gòu)圖.png
  • Subject(目標(biāo))
    目標(biāo)知道它的觀察者。可以有任意多個觀察者觀察同一個目標(biāo)。
    提供注冊和刪除觀察者對象的接口。
  • Observer(觀察者)
    為那些在目標(biāo)發(fā)生改變時需要獲得通知的對象定義一個更新接口。
  • ConcreteSubject(具體目標(biāo))
    將有關(guān)狀態(tài)存入各ConcreteObserver對象。
    當(dāng)它的狀態(tài)發(fā)生改變時,向它的各個觀察者發(fā)出通知。
  • ConcreteObserver(具體觀察者)
    維護(hù)一個指向ConcreteSubject對象的引用。
    存儲有關(guān)狀態(tài),這些狀態(tài)應(yīng)與目標(biāo)的狀態(tài)保持一致。
    實(shí)現(xiàn)Observer的更新接口以使自身狀態(tài)與目標(biāo)的狀態(tài)保持一致。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

目標(biāo)和觀察者的抽象耦合:一個目標(biāo)所知道的僅僅是它又一系列觀察者,每個都符合抽象的Observer類的簡單接口。目標(biāo)不知道任何一個觀察者屬于哪一個具體的類。這樣目標(biāo)和觀察者之間的耦合是抽象的和最小的。

支持廣播通信:不像通常的請求,目標(biāo)發(fā)送的通知不需指定它的接收者。通知被自動廣播給所有已向該目標(biāo)對象登記的有關(guān)對象。目標(biāo)對象并不關(guān)心到底有多少對象對自己感興趣;它唯一的責(zé)任就是通知它的各觀察者。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決于觀察者。

缺點(diǎn)

意外的更新:因?yàn)橐粋€觀察者并不知道其他觀察者的存在,它可能對改變目標(biāo)的最終代價一無所知。在目標(biāo)上一個看似無害的操作可能會引起一系列對觀察者以及依賴于這些觀察者的那些對象的更新。此外,如果依賴準(zhǔn)則的定義或維護(hù)不當(dāng),常常會引起錯誤的更新,這種錯誤通常很難捕捉。

對觀察者模式實(shí)現(xiàn)的深入探討

  • 目標(biāo)與觀察者之間的映射:通常會在目標(biāo)對象中采用一個集合來保存觀察者的注冊信息。
  • 觀察多個目標(biāo):在某些情況下,一個觀察者依賴于多個目標(biāo)可能是有意義的。
  • 誰觸發(fā)更新:目標(biāo)和它的觀察者依賴于通知機(jī)制來保持一致。但到底哪一個對象調(diào)用Notify來觸發(fā)更新?此時有兩個選擇:
    由目標(biāo)對象的狀態(tài)設(shè)定操作在改變目標(biāo)對象的狀態(tài)后自動調(diào)用Notify。這種方法的優(yōu)點(diǎn)是客戶不需要記住要在目標(biāo)對象上調(diào)用Notify,缺點(diǎn)是多個連續(xù)的操作會產(chǎn)生多次連續(xù)的更新,可能效率較低。
    讓客戶負(fù)責(zé)在適當(dāng)?shù)臅r候調(diào)用Notify。這樣做的優(yōu)點(diǎn)是客戶可以在一系列的狀態(tài)改變完成后再一次性地觸發(fā)更新,避免了不必要的中間更新。缺點(diǎn)是給客戶增加了觸發(fā)更新的責(zé)任。由于客戶可能忘記調(diào)用Notify,這種方式較易出錯。
  • 對已刪除目標(biāo)的懸掛引用:刪除一個目標(biāo)時應(yīng)注意不要在其觀察者中遺留對該目標(biāo)的懸掛引用。這種避免懸掛引用的方法是,當(dāng)一個目標(biāo)被刪除時,讓它通知它的觀察者將對該目標(biāo)的引用復(fù)位。一般來說,不能簡單地刪除觀察者,因?yàn)槠渌膶ο罂赡軙盟鼈儯蛘咭部赡芩鼈冞€在觀察其他的目標(biāo)
  • 在發(fā)出通知前確保目標(biāo)的狀態(tài)自身是一致的:在實(shí)現(xiàn)觀察者模式的時候,一定要注意觸發(fā)通知的時機(jī),一般情況下,是在完成了狀態(tài)維護(hù)后觸發(fā),因?yàn)橥ㄖ獣鬟f數(shù)據(jù),不能夠先通知后改數(shù)據(jù),這很容易出問題,會導(dǎo)致觀察者和目標(biāo)對象的狀態(tài)不一致。比如:目標(biāo)一發(fā)出通知,就有觀察者來取值,結(jié)果目標(biāo)還沒有更新數(shù)據(jù),這就明顯造成了錯誤。
  • 顯示地定義感興趣的改變:你可以擴(kuò)展目標(biāo)的注冊接口,讓各觀察者注冊為僅對特定事件感興趣,以提高更新的效率。當(dāng)一個事件發(fā)生時,目標(biāo)僅通知那些已注冊為對該事件感興趣的觀察者。支持這種做法一種途徑是,使用目標(biāo)對象的方面(aspects)的概念。
  • 推模型和拉模型
    ① 推模型:
    目標(biāo)對象主動向觀察者推送目標(biāo)的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是目標(biāo)對象的全部或部分?jǐn)?shù)據(jù)。
    ② 拉模型:
    目標(biāo)對象在通知觀察者的時候,只傳遞少量信息,如果觀察者需要更具體的信息,由觀察者主動到目標(biāo)對象中獲取,相當(dāng)于是觀察者從目標(biāo)對象中拉數(shù)據(jù)。
    一般這種模型的實(shí)現(xiàn)中,會把目標(biāo)對象自身通過update方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。
推模型 VS 拉模型

a) 推模型是假定目標(biāo)對象知道觀察者需要的數(shù)據(jù);而拉模型是目標(biāo)對象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳給觀察者,讓觀察者自己去按需取值。
b) 推模型可能會使得觀察者對象難以復(fù)用,因?yàn)橛^察者定義的update方法是按需而定義的,可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時候,就可能需要提供新的update方法,或者是干脆重新實(shí)現(xiàn)觀察者。
c) 而拉模型就不會造成這樣的情況,因?yàn)槔P拖拢瑄pdate方法的參數(shù)是目標(biāo)對象本身,這基本上是目標(biāo)對象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。

Java中的觀察者模式

Java API 有內(nèi)置的觀察者模式。java.util包(package)內(nèi)包含最基本的Observer接口與Observable類,這和我們的Subject接口與Observer接口很相似。Observer接口與Observable類使用上更方便,因?yàn)樵S多功能都已經(jīng)事先準(zhǔn)備好了。你甚至可以使用推或拉的方式傳送數(shù)據(jù)。


Java API 內(nèi)置觀察者模式.png

我們可以通過繼承Observable類和實(shí)現(xiàn)Observer接口來分別實(shí)現(xiàn)主題和觀察者。然后通過調(diào)用Observable對象的addObserver()方法來添加觀察者,調(diào)用deleteObserver()來移除一個觀察者。

Q:Observable要如何送出通知?
A: 需要兩個步驟:
① 先調(diào)用setChanged()方法,標(biāo)記狀態(tài)已經(jīng)改變的事實(shí)。
② 然后調(diào)用兩種notifyObservers方法中的一個:
a) notifyObsrvers() ———— 用于拉模型;
b) notifyObserves(Object arg) ———— 用于推模型;

Q:Observer如何接受通知?
A:同以前一樣,觀察者實(shí)現(xiàn)了更新的方法,但是方法的簽名不太一樣:
void update(Observable o, Object arg);
參數(shù) Observable o :主題本身當(dāng)作第一個變量,好讓觀察者知道是哪個主題通知它的。若是拉模式,則需要通過該參數(shù)來獲取所需的數(shù)據(jù)。
參數(shù) Object arg :若Observable通過notifyObsrvers()方式發(fā)送通知,則該參數(shù)為null;若Observable通過notifyObserves(Object arg)方式發(fā)送通知,則該參數(shù)為推送過來的數(shù)據(jù)對象。

Q:為什么在Observable發(fā)送notifyObservers前一定要調(diào)動setChanged()方法來標(biāo)記狀態(tài)已經(jīng)改變的事實(shí)了?
A:從notify的源碼能看出,若狀態(tài)標(biāo)志changed未被設(shè)置為true,則不會進(jìn)行對Observer的notify操作了。而setChanged()方法就是來完成對狀態(tài)標(biāo)志changed的設(shè)置的。
而這么做的好處在于,讓你在更新觀察者時有更多的彈性,你可以更適當(dāng)?shù)赝ㄖ^察者。



參考

《Head First 設(shè)計(jì)模式》
《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
《研磨設(shè)計(jì)模式》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容