今天讓咱們來學習一下觀察者模式java
1、認識觀察者模式:異步
咱們都知道報社和雜誌訂閱的關係,報社的業務就是出版報紙,當你向報社訂閱報紙後,只要他們有新的報紙出版,就會給你送來,只要你是他們的訂戶,就會一直收到新的報紙。當你不想再看報紙的時候,取消訂閱,他們就不會再送新報紙來。只要報社還在運營,就會一直有人(或單位)向他們訂閱報紙或取消訂閱報紙。ide
若是你瞭解報紙的訂閱是怎麼回事,其實就知道觀察者模式是怎麼回事,只是名稱不太同樣:出版者改稱爲「主題」(Subject),訂閱者改稱爲「觀察者」(Observer)。學習
下面通道一張圖來更好的瞭解觀察者模式:ui
2、定義this
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的全部依賴者都會收到通知並自動更新。spa
(主題和觀察者定義了一對多的關係。觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知的風格,觀察者可能所以新值而更新。)設計
3、理解並實現觀察者模式code
一、咱們先來看一下觀察者模式的UML類圖:server
二、觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。從而讓對象之間的互相依賴降到了最低。(這也是面向對象設計的一條很是重要的原則)
三、下面咱們經過氣象站的例子來實現觀察者模式
(1)、此氣象站的功能是當氣象數據變化的時候,咱們會經過方法將氣象數據隨時更新並顯示到咱們的佈告板上。此係統中的三個部分是氣象站(獲取實際氣象數據的物理裝置)、WeatherData對象(追蹤來自氣象站的數據,並更新佈告板)和佈告板(顯示目前天氣情況給用戶看)。
WeatherData對象知道如何跟物理氣象站聯繫,以取得更新的數據。WeatherData對象會隨即更新三個佈告板的顯示:目前情況(溫度、溼度、氣壓)、氣象統計和天氣預報。
咱們的工做就是創建一個應用,利用WeatherData對象取得數據,並更新三個佈告板:目前情況、氣象統計和天氣預報。
(2)、下面咱們用代碼來實現咱們的氣象站(這裏並無將整個功能都實現,只是爲了演示觀察者模式)
觀察者接口:
/** * @author fan_rc@suixingpay.com * @description 觀察者接口 * @date 2019/8/28 21:07 */ public interface Observer { /** * @param temp * @param humidity * @param pressure * @description 當主題內容改變時,須要更新觀察者的溫度等狀態 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:16 */ void update(float temp, float humidity, float pressure); }
主題接口:
/** * @author fan_rc@suixingpay.com * @description 主題接口 * @date 2019/8/28 21:08 */ public interface Subject { /** * @param * @return * @throws * @description 註冊成爲觀察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:09 */ void registerObserver(Observer observer); /** * @param * @return * @throws * @description 刪除觀察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:10 */ void removeObserver(Observer observer); void notifyObservers(); }
觀察者1:統計顯示類
/** * @author fan_rc@suixingpay.com * @description 統計顯示 * @date 2019/8/28 21:33 */ public class StatisticsDisplay implements Observer { private Subject weatherData; public StatisticsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("統計顯示收到天氣數據變化消息" + temp + "-" + humidity + "-" + pressure); } }
觀察者2:實時顯示類
/** * @description 實時顯示(觀察者) * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:25 */ public class CurrentConditionsDisplay implements Observer { private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("實時顯示觀察者收到天氣數據變化消息" + temp + "-" + humidity + "-" + pressure); } }
主題實現類:
/** * @author fan_rc@suixingpay.com * @description 天氣數據 主題實現類 * @date 2019/8/28 21:17 */ public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { int i = observers.indexOf(observer); if (i >= 0) { observers.remove(observer); } } @Override public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = observers.get(i); observer.update(temperature, humidity, pressure); } } /** * @param * @return * @throws * @description 當從氣象站獲得更新觀測值時,咱們通知觀察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:23 */ public void measurementsChanged() { notifyObservers(); } /** * 設置天氣數據(同時,改變天氣數據,通知觀察者) * * @param temperature * @param humidity * @param pressure */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
咱們的氣象站類:
/** * @description 氣象站 * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:27 */ public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); weatherData.setMeasurements(10, 11, 12); } }
咱們來看一下運行結果:
以上代碼變簡單的實現了咱們的觀察者模式。
在java中呢,其實已經內置了觀察者模式。java.util包(package)內包含最基本的Observer接口與Observable類,這和咱們的Subject接口與Observer接口很類似。Observer接口與Observable類使用上更方便,由於許多功能都已經事先準備好了。
下面,咱們用java內置的觀察者來實現一下
觀察者:
/** * @author fan_rc@suixingpay.com * @description 基於java的觀察者類實現觀察者 * @date 2019/8/29 21:06 */ public class CurrentConditionsDisplayJava implements Observer { private Observable observable; public CurrentConditionsDisplayJava(Observable observable) { this.observable = observable; observable.addObserver(this); } @Override public void update(Observable o, Object arg) { if (o instanceof WeatherDataJava) { WeatherDataJava weatherDataJava = (WeatherDataJava) o; System.out.println(arg); } } }
主題類:
/** * @author fan_rc@suixingpay.com * @description 基於Java內部的Observable類實現被觀察者類 * @date 2019/8/29 21:04 */ public class WeatherDataJava extends Observable { private float temperature; private float humidity; private float pressure; /** * @description 當從氣象站獲得更新觀測值時,咱們通知觀察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:23 */ public void measurementsChanged() { setChanged(); notifyObservers(temperature); } /** * 設置天氣數據(同時,改變天氣數據,通知觀察者) * * @param temperature * @param humidity * @param pressure */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
氣象站:
/** * @description 氣象站 * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:27 */ public class WeatherStation { public static void main(String[] args) { WeatherDataJava weatherDataJava = new WeatherDataJava(); CurrentConditionsDisplayJava currentConditionsDisplayJava = new CurrentConditionsDisplayJava(weatherDataJava); weatherDataJava.setMeasurements(10, 11, 12); } }
運行結果:
4、優缺點
一、優勢:(1)觀察者和被觀察者是抽象耦合的;(2)創建一套觸發機制。
二、缺點:(1)若是一個被觀察者對象有不少的直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。 (2)若是在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。 (3)觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
5、使用場景
一、一個抽象模型有兩個方面,其中一個方面依賴於另外一個方面。將這些方面封裝在獨立的對象中使它們能夠各自獨立地改變和複用。
二、一個對象的改變將致使其餘一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,能夠下降對象之間的耦合度。
三、一個對象必須通知其餘對象,而並不知道這些對象是誰。
四、須要在系統中建立一個觸發鏈,A對象的行爲將影響B對象,B對象的行爲將影響C對象……,可使用觀察者模式建立一種鏈式觸發機制。
6、注意事項
一、使用jdk自帶的觀察者模式是有缺點的:(1)Observable是一個類,而不是一個接口,致使Observable類的擴展性不高,不如本身實現的觀察者模式靈活。(2)Observable將某些方法保護了起來(setChanged()和clearChanged()爲protected),這意味着除非繼承自Observable,不然將有關鍵的方法不能調用。致使沒法經過組合的方式使其它類得到Observable類的功能。
二、避免循環引用。
三、若是順序執行,某一觀察者錯誤會致使系統卡殼,通常採用異步方式。