-
定義
觀察者模式定義了對(duì)象之間的一對(duì)多依賴,這樣一來(lái),當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),他的所有依賴著都會(huì)收到通知并自動(dòng)更新.
-
考慮這樣一個(gè)場(chǎng)景,氣象中心會(huì)隔一段時(shí)間更新一次氣象數(shù)據(jù),比如氣溫,氣壓,前端展示的頁(yè)面要能夠發(fā)現(xiàn)氣象數(shù)據(jù)更新之后就獲取到新數(shù)據(jù)并展示,比較一般的辦法就是實(shí)現(xiàn)幾個(gè)不同的類,比如展示氣溫,氣壓之類的,然后一有更新就調(diào)用每個(gè)類的更新方法來(lái)更新,這樣的辦法很不利于擴(kuò)展,比如說(shuō)我要添加一個(gè)展示相關(guān)的類,我就需要在氣象數(shù)據(jù)的相關(guān)類中做修改,這樣讓兩個(gè)類之間緊緊的關(guān)聯(lián)在一起,也就是常說(shuō)的緊耦合了,很不利于當(dāng)新需求過(guò)來(lái)時(shí)進(jìn)行擴(kuò)展;
當(dāng)然觀察者模式就是用來(lái)解決類似的問題的,它利用一個(gè)主題來(lái)表示數(shù)據(jù)發(fā)布更新的源頭,用多個(gè)實(shí)現(xiàn)自一個(gè)接口的觀察者來(lái)負(fù)責(zé)獲取更新數(shù)據(jù)并處理的類;我們只需要針對(duì)當(dāng)前主題存儲(chǔ)它在每次更新時(shí)需要通知的觀察者信息(同一接口);在數(shù)據(jù)發(fā)生更新時(shí),只需要遍歷存儲(chǔ)的數(shù)據(jù)并調(diào)用當(dāng)前觀察者類的更新方法即可讓觀察者獲取更新數(shù)據(jù),也可以在觀察者不想獲取新數(shù)據(jù)時(shí)進(jìn)行注銷等操作,這里為什么解決了上述問題,也就是解耦合?因?yàn)槲覀儾恍枰烂總€(gè)觀察者實(shí)現(xiàn)的具體類,只需要讓他們繼承自同一接口,每次更新時(shí)調(diào)用接口的更新方法即可,具體怎么更新由每個(gè)觀察者自己來(lái)實(shí)現(xiàn).
-
UML圖
-
實(shí)現(xiàn)
主題接口:
interface Subject{ /** * 注冊(cè)觀察者 * @param observer * @return */ public boolean registerObserver(Observer observer); /** * 移除觀察者 * @param observer * @return */ public boolean removeObserver(Observer observer); int notifyObserver(); }
實(shí)際主題:
class Subject_a implements Subject{ // 觀察者的映射 private Map<String ,Observer> observers = new HashMap<>(); private String subjectName; // 數(shù)據(jù) private int a, b, c; Subject_a(String subjectName){ this.subjectName = subjectName; } @Override public boolean registerObserver(Observer observer) { if (observers.get(observer.name()) != null){ System.out.println(observer.name()+"已被其他觀察者占用,請(qǐng)重新注冊(cè)!"); return false; } System.out.println(subjectName+"注冊(cè)"+observer.name()); observers.put(observer.name(),observer); return true; } @Override public boolean removeObserver(Observer observer) { if (observers.get(observer.name()) == null){ System.out.println("觀察者"+observer.name()+"不存在,請(qǐng)重新注銷!"); return false; } System.out.println(subjectName+"移除"+observer.name()); observers.remove(observer.name()); return true; } /** * 通知觀察者 * @return */ @Override public int notifyObserver() { System.out.println(subjectName+"發(fā)布更新,更新數(shù)據(jù)為 a:"+a+" b:"+b+" c:"+c); for (Map.Entry<String ,Observer> entry : observers.entrySet()){ entry.getValue().update(subjectName,a,b,c); } return observers.size(); } public void dataChanged(int a, int b, int c){ this.a = a; this.b = b; this.c = c; int sum = notifyObserver(); System.out.println("接收到"+subjectName+"的人數(shù):"+sum); } }
觀察者接口:
interface Observer{ /** * 更新數(shù)據(jù) * @param subjectName * @param a * @param b * @param c */ void update(String subjectName, int a, int b, int c); String name(); }
實(shí)際觀察者:
class ObserverA implements Observer{ String observerName; public String getObserverName() { return observerName; } ObserverA(String observerName) { this.observerName = observerName; } @Override public void update(String subjectName,int a, int b, int c) { System.out.println(observerName+"收到來(lái) 自"+subjectName+"的更新,更新數(shù)據(jù)為 a:"+a+" b:"+b+" c:"+c); } @Override public String name() { return observerName; } }
寫個(gè)代碼測(cè)試一下:
public static void main(String[] args) { Subject_a subject_a = new Subject_a("主題1"); ObserverA observerA = new ObserverA("觀察者1"); ObserverA observerA1 = new ObserverA("觀察者2"); subject_a.registerObserver(observerA); subject_a.registerObserver(observerA1); subject_a.dataChanged(1,2,3); subject_a.removeObserver(observerA); subject_a.dataChanged(3,4,5); }
這里我們只用了一個(gè)主題,可以實(shí)現(xiàn)多個(gè)主題,來(lái)注冊(cè)不同的觀察者,只需要實(shí)現(xiàn)接口即可;我們?cè)谥黝}1上注冊(cè)了兩個(gè)觀察者1,2,并更新一次數(shù)據(jù),觀察者1,2都會(huì)接到數(shù)據(jù)并更新;接下來(lái)移除觀察者1,再更新一次數(shù)據(jù),會(huì)發(fā)現(xiàn)觀察者1接收不到新的數(shù)據(jù)了.打印一下輸出:
-
談一談優(yōu)缺點(diǎn):
優(yōu)點(diǎn):最重要的一點(diǎn),觀察者模式將主題和觀察者解耦合,主題不需要了解觀察者內(nèi)部的實(shí)現(xiàn)即可傳輸數(shù)據(jù)過(guò)去;一對(duì)多,一個(gè)主題每次更新數(shù)據(jù)時(shí)可以傳輸給多個(gè)觀察者,觀察者想要接收更新只需要調(diào)用注冊(cè)方法,想要不接收更新只需要調(diào)用注銷方法,擴(kuò)展起來(lái)很方便.
缺點(diǎn):由于主題不知道觀察者內(nèi)部的實(shí)現(xiàn),所以主題每次都會(huì)把所有更新的數(shù)據(jù)傳給觀察者,有可能某一觀察者只需要一點(diǎn)數(shù)據(jù)卻接受了全部,一浪費(fèi)資源,二有可能會(huì)產(chǎn)生安全相關(guān)的問題;其次每次都是主題數(shù)據(jù)更新時(shí)立馬傳給觀察者,沒有考慮有可能此時(shí)觀察者正在高負(fù)載工作,頻繁地通知觀察者會(huì)讓觀察者接收頻率太快,沒有觀察者自己想獲取數(shù)據(jù)時(shí)請(qǐng)求更新更考慮到了性能的問題.
?