原文鏈接:java設計模式詳細講解-觀察者模式
如果想要更加詳細的 Java中的23種設計模式視頻資源,請點擊鏈接:Java中的23種設計模式視頻資源下載
1、觀察者模式原理:
對象之間存在多對一的依賴院系,被依賴方向多個依賴放分發、發佈事件時的一種程序設計解決方案,此時,被依賴方的對象稱之爲Subject,依賴放的對象稱之爲ObServ er,Subject向ObServer通知。
例如微信公衆號的關注,關注者就是ObServer,被關注的公衆號就是Subject。當關注者關賬了一個公衆號,那麼公衆號在推送文章時,就會想所有關注者推送這篇文章;反之,如果關注者取消關注,則公衆號在推送文章時不再向他推送文章。類似的場景還有訂牛奶等場景。
觀察者模式中Subject對象一般包含如下事件:
1、添加事件(當有新的ObServer接入時調用)
2、移除事件(當有ObServer停止接入時調用)
3、通知/發佈事件(當有新事件/消息發佈時,通過此接口通知所有的ObServer)
而ObServer則提供如下事件/方法:
1、事件/消息接收(用於接收Subject推送的事件/消息)
Subject對象和ObServer對象的關係如下:
2、java 內置觀察者
Java內置觀察者和上述的觀察者模式原理大致相同,
只是在java內置觀察者模式中,被依賴對象稱之爲ObServable,且ObServable是一個類,而不是一個接口;開發者在使用的過程中,要繼承這個類;並且由於ObServable已經實現了一部分事件,例如增加、移除、通知,我們在繼承這個類之後,就無須考慮實現這些方法。
依賴的對象也稱之爲ObServer(注意,這個是一個接口,而不是一個類,之所以設計爲接口,是因爲在不同的項目實現的時候,在接收事件中要處理的邏輯可能不盡相同),
在ObServer觀察者可以選擇是等待ObServable直接推送消息內容,還是等待ObServable通知,然後自己主動向ObServable拉去消息,這是在Java內置觀察者中的可以實現的。
Javan內置觀察者添加ObServer後推送是先進後出,即先添加的會放在後面推送,而後進來的會先被調用通知。
3、觀察者模式和Java內置觀察者實現示例演示
3.1、觀察者模式實現
由於觀察者模式爲自己實現,本案例將通過接口來實現
被觀察者接口
package com.beBianMin.shareLogger.animal.ObserverMode.subject; import com.beBianMin.shareLogger.animal.ObserverMode.observer.Observer; /** * 被觀察者接口 * @author 郭鵬飛 * */ public interface Subject { /** * 註冊新的觀察者 * @param observer * @return */ public void registerObServer(Observer observer); /** * 已拆除觀察者 * @param observer */ public void removeObserver(Observer observer); /** * 通知觀察者 */ public void notifyObservers(); }
該接口有三個方法:註冊、移除、通知
被觀察者實現類:
package com.beBianMin.shareLogger.animal.ObserverMode.subject; import java.util.ArrayList; import java.util.List; import com.beBianMin.shareLogger.animal.ObserverMode.observer.Observer; /** * 被觀察者實現 * @author 郭鵬飛 * */ public class CurrentSubject implements Subject{ //參數1 private float temperatrue; //參數2 private float pressure; //參數3 private float humodity; //設置一個觀察者的列表 private List<Observer> observers; public CurrentSubject() { observers = new ArrayList<Observer>(); } public float getTemperatrue() { return temperatrue; } public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } public void setData(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; dataChage(); } public void dataChage() { notifyObservers(); } @Override public void registerObServer(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { if(observers.contains(observer)) { observers.remove(observer); } } @Override public void notifyObservers() { for(Observer observer:observers) { observer.update(getTemperatrue(), getPressure(), getHumodity()); } } }
這個類看看似有點亂,其實上面三個參數
//參數1 private float temperatrue; //參數2 private float pressure; //參數3 private float humodity;
是模擬要通知觀察者的數據,這個可以隨意設置,可以按照你需要通知的數據設置自己的對象格式。
在這個類裏面要注意的是要設置一個用於存儲觀察者的容器對象並駛離話它
//設置一個觀察者的列表 private List<Observer> observers; public CurrentSubject() { observers = new ArrayList<Observer>(); }
在這個實現類中註冊方法中,把新的觀察者放入到這個容易中,在移除方法中把觀察者從這個容器中移除掉,在通知方法中遍歷所有的貫徹着,講上面三個參數傳遞到觀察者中。
並且我們現在假設在初始化上面所說的三個參數時,假設數據就是變化了,然後通過調用dataChage()方法來調用器notifyObservers方法來通知觀察者。
觀察者接口
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 觀察者接口 * @author 郭鵬飛 * */ public interface Observer { /** * 觀察者被通知的方法 * @param temperatrue * @param pressure * @param humodity */ public void update(float temperatrue,float pressure ,float humodity); }
該接口只有一個接口,用於接收被觀察者發佈的數據
觀察者試下你類,本案例模擬兩個觀察者來實現推送:
第一個觀察者
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 第一個觀察者 * @author 郭鵬飛 * */ public class CurrentObserver1 implements Observer{ @Override public void update(float temperatrue, float pressure, float humodity) { System.out.println("第一個觀察者被通知的結果,temperatrue:"+temperatrue); System.out.println("第一個觀察者被通知的結果,pressure:"+pressure); System.out.println("第一個觀察者被通知的結果,FloatArrayAssert:"+humodity); } }
第二個觀察者
package com.beBianMin.shareLogger.animal.ObserverMode.observer; /** * 第一個觀察者 * @author 郭鵬飛 * */ public class CurrentObserver2 implements Observer{ @Override public void update(float temperatrue, float pressure, float humodity) { System.out.println("第二個觀察者被通知的結果,temperatrue:"+temperatrue); System.out.println("第二個觀察者被通知的結果,pressure:"+pressure); System.out.println("第二個觀察者被通知的結果,FloatArrayAssert:"+humodity); } }
這兩個觀察者在實現的方法中只是打印了以下接收到的通知數據。
測試代碼如下:
package com.beBianMin.shareLogger.animal.ObserverMode; import com.beBianMin.shareLogger.animal.ObserverMode.observer.CurrentObserver1; import com.beBianMin.shareLogger.animal.ObserverMode.observer.CurrentObserver2; import com.beBianMin.shareLogger.animal.ObserverMode.subject.CurrentSubject; public class TestClass { public static void main(String[] args) { CurrentObserver1 currentObserver1 ; CurrentObserver2 currentObserver2; CurrentSubject currentSubject ; System.out.println("測試添加除觀察者================="); currentObserver1 = new CurrentObserver1(); currentObserver2 = new CurrentObserver2(); currentSubject = new CurrentSubject(); currentSubject.registerObServer(currentObserver1); currentSubject.registerObServer(currentObserver2); currentSubject.setData(1, 2, 3); System.out.println("測試移除觀察者================="); currentSubject.removeObserver(currentObserver2); currentSubject.setData(5, 6, 7); } }
運行代碼結果如下:
測試添加除觀察者================= 第一個觀察者被通知的結果,temperatrue:1.0 第一個觀察者被通知的結果,pressure:2.0 第一個觀察者被通知的結果,FloatArrayAssert:3.0 第二個觀察者被通知的結果,temperatrue:1.0 第二個觀察者被通知的結果,pressure:2.0 第二個觀察者被通知的結果,FloatArrayAssert:3.0 測試移除觀察者================= 第一個觀察者被通知的結果,temperatrue:5.0 第一個觀察者被通知的結果,pressure:6.0 第一個觀察者被通知的結果,FloatArrayAssert:7.0
發現第一次測試中,兩個貫徹着都被通知到了,第二次測試中移除了第二個觀察者,再次通知,只能通知到第一個觀察者。並且發現一個現象:誰先被添加,誰先被通知(先進先出),這個現象在Java內置觀察者中並不一致
3.2、Java內置觀察者實現
在上面講述Java內置觀察者時咱們已經說過,Java內置了觀察者(ObServable類)和被觀察者(ObServer 接口),因此我們使用Java內置觀察者實現觀察者時就很簡單。
由於Java內置觀察者中被觀察者向觀察者通知時需要用一個對象來傳遞,因此我們定義一個作爲傳遞數據的載體pojo:
package com.beBianMin.beBianMin.ObserverMode4Java.pojo; public class Data4Java { // 參數1 private float temperatrue; // 參數2 private float pressure; // 參數3 private float humodity; public Data4Java(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; } public float getTemperatrue() { return temperatrue; } public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } }
定義被觀察者
package com.beBianMin.beBianMin.ObserverMode4Java.ObSeervable; import java.util.Observable; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 被觀察者實現 * * @author 郭鵬飛 */ public class CurrentSubject extends Observable { // 參數1 private float temperatrue; // 參數2 private float pressure; // 參數3 private float humodity; public void setTemperatrue(float temperatrue) { this.temperatrue = temperatrue; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } public float getHumodity() { return humodity; } public void setHumodity(float humodity) { this.humodity = humodity; } public void setData(float temperatrue, float pressure, float humodity) { this.temperatrue = temperatrue; this.pressure = pressure; this.humodity = humodity; dataChage(); } public void dataChage() { // setChanged方法會設置一個變量爲true,它的作用是: // java內置觀察者在通知的時候,會判斷這個變量是不是爲true,如果爲true,則會通知,否則不會通知,而改製爲人爲false // 在同之前先調用setChanget()方法,表示數據是有更新的,時代嗎更具有靈活性 // 你可以在通知代碼中設置一個是否需要通知觀察者的一個條件, // 例如當上面三個參數範圍超過某個範圍纔會需要通知, // 這是就需要調用this.setChange()方法,表明參數右邊,可以通知 this.setChanged(); // 方式1、只通知觀察者,然後觀察者調用被觀察者拉去通知消息 // this.notifyObservers(); // 方式2、直接將一個對象傳遞給觀察者 this.notifyObservers(new Data4Java(getHumodity(), getPressure(), getPressure())); } }
注意上面的這個被觀察者,由於上文咱們說了Java內置觀察者(Observable ,爲一個類)已經實現了添加、移除、通知觀察者的方法,因此咱們就不想要重載這兩個方法了,而只需要自己定義如何調用觀察者邏輯即可,就是上面的setData方法
上面的這個觀察者實現中要注意一下幾點:
1、需要繼承Observable類
2、不需要重載添加、移除、通知方法
3、通知觀察者有兩個方法/代碼 notifyObservers()和notifyObservers(Object arg),這兩個方法就是上面我說的你可以選擇通知觀察者數據有變化還是僅僅直接將要通知的內容傳遞進去
4、在調用3中的通知方法時,請首先調用setChanged();方法,這個很重要。原因在於,Java內置觀察者在通知觀察者之前,會通過一個內置的變量(true/false)來決定是否通知觀察者,而這個變量默認爲false(有興趣的可以看下源碼),如果你在調用3中的通知方法前不加此方法,Java內置觀察則會將不會去通知觀察者。這個方法的作用就是:讓你的通知邏輯更加靈活(例如自己設置一個是否通知觀察這的閾值)。
接下來實現第一個觀察者:
package com.beBianMin.beBianMin.ObserverMode4Java.ObServer; import java.util.Observable; import java.util.Observer; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 第一個觀察者 * * @author 郭鵬飛 */ public class CurrentObserver1 implements Observer { @Override public void update(Observable o, Object arg) { // 觀察者中將通知到的對象轉換爲實際對象 Data4Java data4Java = (Data4Java) arg; System.out.println("第一個觀察者被通知的結果,temperatrue:" + data4Java.getTemperatrue()); System.out.println("第一個觀察者被通知的結果,pressure:" + data4Java.getPressure()); System.out.println("第一個觀察者被通知的結果,FloatArrayAssert:" + data4Java.getHumodity()); } }
第二個觀察者:
package com.beBianMin.beBianMin.ObserverMode4Java.ObServer; import java.util.Observable; import java.util.Observer; import com.beBianMin.beBianMin.ObserverMode4Java.pojo.Data4Java; /** * 第二個觀察者 * * @author 郭鵬飛 */ public class CurrentObserver2 implements Observer { @Override public void update(Observable o, Object arg) { // 觀察者中將通知到的對象轉換爲實際對象 Data4Java data4Java = (Data4Java) arg; System.out.println("第二個觀察者被通知的結果,temperatrue:" + data4Java.getTemperatrue()); System.out.println("第二個觀察者被通知的結果,pressure:" + data4Java.getPressure()); System.out.println("第二個觀察者被通知的結果,FloatArrayAssert:" + data4Java.getHumodity()); } }
上面咱們已經說了,觀察者需要實現Observer接口(注意,是一個接口,之所以爲接口,是因爲他不知道你接收到數據後要幹嘛,因此只能讓你重載他的接收方法,然後你自己處理)
測試代碼:
package com.beBianMin.beBianMin.ObserverMode4Java; import com.beBianMin.beBianMin.ObserverMode4Java.ObSeervable.CurrentSubject; import com.beBianMin.beBianMin.ObserverMode4Java.ObServer.CurrentObserver1; import com.beBianMin.beBianMin.ObserverMode4Java.ObServer.CurrentObserver2; public class TestClass { public static void main(String[] args) { CurrentObserver1 currentObserver1; CurrentObserver2 currentObserver2; CurrentSubject currentSubject; System.out.println("測試添加除觀察者================="); currentObserver1 = new CurrentObserver1(); currentObserver2 = new CurrentObserver2(); currentSubject = new CurrentSubject(); currentSubject.addObserver(currentObserver1); currentSubject.addObserver(currentObserver2); currentSubject.setData(1, 2, 3); System.out.println("測試移除觀察者================="); currentSubject.deleteObserver(currentObserver2); currentSubject.setData(5, 6, 7); } }
運行結果如下:
測試添加除觀察者================= 第二個觀察者被通知的結果,temperatrue:3.0 第二個觀察者被通知的結果,pressure:2.0 第二個觀察者被通知的結果,FloatArrayAssert:2.0 第一個觀察者被通知的結果,temperatrue:3.0 第一個觀察者被通知的結果,pressure:2.0 第一個觀察者被通知的結果,FloatArrayAssert:2.0 測試移除觀察者================= 第一個觀察者被通知的結果,temperatrue:7.0 第一個觀察者被通知的結果,pressure:6.0 第一個觀察者被通知的結果,FloatArrayAssert:6.0
通過上面打印出的結果,我們可以發現如下現象:
1、通知結果爲先進後出,即誰先被添加進去,誰會被放在後面通知
2、被觀察者不能多重繼承(由於它已經繼承了一個類),這是Java內置觀察者的一個缺點
4、觀察者模式的關鍵點
1、觀察者模式實現了松偶爾、高內聚,高度隔離
2、使用java內置觀察者可以省去添加、移除、通知的方法,缺點就是不容易定製化開發,無法實現多重繼承