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)一個抽象模型有兩個方面怒允,其中一個方面依賴于另一個方面埂软。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復(fù)用。
② 當(dāng)對一個對象的改變需要同時改變其它對象误算,而不知道具體有多少對象有待改變仰美。
③ 當(dāng)一個對象必須通知其它對象,而它又不能假定其它對象是誰儿礼。換言之咖杂,你不希望這些對象是緊密耦合的。
結(jié)構(gòu)
-
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(具體觀察者)
維護一個指向ConcreteSubject對象的引用。
存儲有關(guān)狀態(tài)乍桂,這些狀態(tài)應(yīng)與目標(biāo)的狀態(tài)保持一致冲杀。
實現(xiàn)Observer的更新接口以使自身狀態(tài)與目標(biāo)的狀態(tài)保持一致效床。
優(yōu)缺點
優(yōu)點
① 目標(biāo)和觀察者的抽象耦合:一個目標(biāo)所知道的僅僅是它又一系列觀察者,每個都符合抽象的Observer類的簡單接口权谁。目標(biāo)不知道任何一個觀察者屬于哪一個具體的類剩檀。這樣目標(biāo)和觀察者之間的耦合是抽象的和最小的。
② 支持廣播通信:不像通常的請求旺芽,目標(biāo)發(fā)送的通知不需指定它的接收者沪猴。通知被自動廣播給所有已向該目標(biāo)對象登記的有關(guān)對象。目標(biāo)對象并不關(guān)心到底有多少對象對自己感興趣采章;它唯一的責(zé)任就是通知它的各觀察者运嗜。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決于觀察者共缕。
缺點
① 意外的更新:因為一個觀察者并不知道其他觀察者的存在洗出,它可能對改變目標(biāo)的最終代價一無所知。在目標(biāo)上一個看似無害的操作可能會引起一系列對觀察者以及依賴于這些觀察者的那些對象的更新图谷。此外翩活,如果依賴準(zhǔn)則的定義或維護不當(dāng),常常會引起錯誤的更新便贵,這種錯誤通常很難捕捉菠镇。
對觀察者模式實現(xiàn)的深入探討
- 目標(biāo)與觀察者之間的映射:通常會在目標(biāo)對象中采用一個集合來保存觀察者的注冊信息。
- 觀察多個目標(biāo):在某些情況下承璃,一個觀察者依賴于多個目標(biāo)可能是有意義的利耍。
- 誰觸發(fā)更新:目標(biāo)和它的觀察者依賴于通知機制來保持一致。但到底哪一個對象調(diào)用Notify來觸發(fā)更新盔粹?此時有兩個選擇:
① 由目標(biāo)對象的狀態(tài)設(shè)定操作在改變目標(biāo)對象的狀態(tài)后自動調(diào)用Notify隘梨。這種方法的優(yōu)點是客戶不需要記住要在目標(biāo)對象上調(diào)用Notify,缺點是多個連續(xù)的操作會產(chǎn)生多次連續(xù)的更新舷嗡,可能效率較低轴猎。
② 讓客戶負(fù)責(zé)在適當(dāng)?shù)臅r候調(diào)用Notify。這樣做的優(yōu)點是客戶可以在一系列的狀態(tài)改變完成后再一次性地觸發(fā)更新进萄,避免了不必要的中間更新捻脖。缺點是給客戶增加了觸發(fā)更新的責(zé)任。由于客戶可能忘記調(diào)用Notify中鼠,這種方式較易出錯可婶。 - 對已刪除目標(biāo)的懸掛引用:刪除一個目標(biāo)時應(yīng)注意不要在其觀察者中遺留對該目標(biāo)的懸掛引用。這種避免懸掛引用的方法是援雇,當(dāng)一個目標(biāo)被刪除時矛渴,讓它通知它的觀察者將對該目標(biāo)的引用復(fù)位。一般來說惫搏,不能簡單地刪除觀察者具温,因為其他的對象可能會引用它們盗舰,或者也可能它們還在觀察其他的目標(biāo)。
- 在發(fā)出通知前確保目標(biāo)的狀態(tài)自身是一致的:在實現(xiàn)觀察者模式的時候桂躏,一定要注意觸發(fā)通知的時機,一般情況下川陆,是在完成了狀態(tài)維護后觸發(fā)剂习,因為通知會傳遞數(shù)據(jù),不能夠先通知后改數(shù)據(jù)较沪,這很容易出問題鳞绕,會導(dǎo)致觀察者和目標(biāo)對象的狀態(tài)不一致。比如:目標(biāo)一發(fā)出通知尸曼,就有觀察者來取值们何,結(jié)果目標(biāo)還沒有更新數(shù)據(jù),這就明顯造成了錯誤控轿。
- 顯示地定義感興趣的改變:你可以擴展目標(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ù)磨德。
一般這種模型的實現(xiàn)中缘回,會把目標(biāo)對象自身通過update方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時候典挑,就可以通過這個引用來獲取了酥宴。
推模型 VS 拉模型
a) 推模型是假定目標(biāo)對象知道觀察者需要的數(shù)據(jù);而拉模型是目標(biāo)對象不知道觀察者具體需要什么數(shù)據(jù)您觉,沒有辦法的情況下拙寡,干脆把自身傳給觀察者,讓觀察者自己去按需取值琳水。
b) 推模型可能會使得觀察者對象難以復(fù)用肆糕,因為觀察者定義的update方法是按需而定義的灾挨,可能無法兼顧沒有考慮到的使用情況那伐。這就意味著出現(xiàn)新情況的時候,就可能需要提供新的update方法,或者是干脆重新實現(xiàn)觀察者作谚。
c) 而拉模型就不會造成這樣的情況,因為拉模型下邦泄,update方法的參數(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類使用上更方便办斑,因為許多功能都已經(jīng)事先準(zhǔn)備好了。你甚至可以使用推或拉的方式傳送數(shù)據(jù)杆逗。
我們可以通過繼承Observable類和實現(xiàn)Observer接口來分別實現(xiàn)主題和觀察者乡翅。然后通過調(diào)用Observable對象的addObserver()方法來添加觀察者,調(diào)用deleteObserver()來移除一個觀察者髓迎。
Q:Observable要如何送出通知峦朗?
A: 需要兩個步驟:
① 先調(diào)用setChanged()方法,標(biāo)記狀態(tài)已經(jīng)改變的事實排龄。
② 然后調(diào)用兩種notifyObservers方法中的一個:
a) notifyObsrvers() ———— 用于拉模型波势;
b) notifyObserves(Object arg) ———— 用于推模型;
Q:Observer如何接受通知?
A:同以前一樣橄维,觀察者實現(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)改變的事實了床牧?
A:從notify的源碼能看出,若狀態(tài)標(biāo)志changed未被設(shè)置為true遭贸,則不會進行對Observer的notify操作了戈咳。而setChanged()方法就是來完成對狀態(tài)標(biāo)志changed的設(shè)置的。
而這么做的好處在于,讓你在更新觀察者時有更多的彈性著蛙,你可以更適當(dāng)?shù)赝ㄖ^察者删铃。
參考
《Head First 設(shè)計模式》
《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
《研磨設(shè)計模式》