定義
關于定義,最準确的莫過于Head First設計模式中寫到的。
觀察者模式定義了一個一對多的依賴關系,讓一個或多個觀察者對象監聽一個主題對象。這樣一來,當被觀察者狀态發生改變時,需要通知相應的觀察者,使這些觀察者對象能夠自動更新。
關鍵要素
主題
主題是觀察者觀察的對象,一個主題必須具備下面三個特征。
- 持有監聽的觀察者的引用
- 支援增加和删除觀察者
- 主題狀态改變,通知觀察者
觀察者
當主題發生變化,收到通知進行具體的處理是觀察者必須具備的特征。
為什麼要用這種模式
這裡舉一個例子來說明,牛奶送奶站就是主題,訂奶客戶為監聽者,客戶從送奶站訂閱牛奶後,會每天收到牛奶。如果客戶不想訂閱了,可以取消,以後就不會收到牛奶。
松耦合
- 觀察者增加或删除無需修改主題的代碼,隻需調用主題對應的增加或者删除的方法即可。
- 主題隻負責通知觀察者,但無需了解觀察者如何處理通知。舉個例子,送奶站隻負責送遞牛奶,不關心客戶是喝掉還是洗臉。
- 觀察者隻需等待主題通知,無需觀察主題相關的細節。還是那個例子,客戶隻需關心送奶站送到牛奶,不關心牛奶由哪個快遞人員,使用何種交通工具送達。
通知不錯過
由于被動接受,正常情況下不會錯過主題的改變通知。而主動擷取的話,由于時機選取問題,可能導緻錯過某些狀态。
Java實作
Java中有觀察者模式使用的API
- java.util.Observable 這是一個類,而非接口,主題需要繼承這個類。
- java.util.Observer 這是一個接口,監聽者需要實作這個接口。
示例代碼
上述代碼完成了
- 将consumer加入主題provider的觀察者行列
- provider設定狀态變化,通知持有的觀察者
- 觀察者consumer收到通知,列印日志處理
setChanged為何物
其實上述代碼中存在這樣一處代碼
setChanged();
,如果在通知之前沒有調用這個方法,觀察者是收不到通知的,這是為什麼呢?
這裡我們看一下setChanged的源碼
很簡單,然後找一下誰使用changed這個值
notifyObservers的代碼
但是為什麼要加入這樣一個開關呢?可能原因大緻有三點
1.篩選有效通知,隻有有效通知可以調用setChanged。比如,我的微信朋友圈一條狀态,好友A點贊,後續該狀态的點贊和評論并不是每條都通知A,隻有A的好友觸發的操作才會通知A。
2.便于撤銷通知操作,在主題中,我們可以設定很多次setChanged,但是在最後由于某種原因需要取消通知,我們可以使用clearChanged輕松解決問題。
3.主動權控制,由于setChanged為protected,而notifyObservers方法為public,這就導緻存在外部随意調用notifyObservers的可能,但是外部無法調用setChanged,是以真正的控制權應該在主題這裡。
主動擷取
觀察者模式即所謂的推送方式,然而推送并非完美無缺。比如主題變化會推送大量的資料,而其中的一些觀察者隻需要某項資料,此時觀察者就需要在具體實作中花費時間篩選資料。
這确實是個問題,想要解決也不難,需要主題為某些資料提供getter方法,觀察者隻需調用getter取資料處理即可。
不足與隐患
主要的問題表現在記憶體管理上,主要由以下兩點
- 主題持有觀察者的引用,如果未正常處理從主題中删除觀察者,會導緻觀察者無法被回收。
- 如果觀察者具體實作代碼有問題,會導緻主題和觀察者對象形成循環引用,在某些采用引用計數的垃圾回收器可能導緻無法回收。