觀察者模式java
定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的全部依賴者都會收到通知並自動更新。算法
出版者+訂閱者=觀察者模式。以報社與訂報人爲例:編程
舉個簡單的例子來描述觀察者模式。dom
某某市要創建空氣質量站,購買了最新型的空氣質量監測儀來實時監測城市的空氣質量。並開放接口將空氣質量提供給全部須要的人或公司。大致結構以下:ide
空氣質量站(AirQualityStations)知道如何採集空氣質量檢測儀監測到的數據,獲得數據後發送給PM2.5數據網(PM2d5Display)和空氣質量發佈網(AQIDisplay)。函數
功能很簡單,首先看一段實現的代碼(錯誤的示範):測試
// 空氣質量數據更新。 // 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。 public void AirQualityChanged() { // 將最新的空氣質量數據更新給這兩個網站。 pm2d5Display.Update(pollutants); aqiDisplay.Update(pollutants); }
在這個實現中,犯了幾個錯誤:網站
若是使用個觀察者模式,將會很好的解決這些問題,觀察者模式以鬆耦合的方式定義了一系列對象之間的一對多關係。當一個對象改變狀態,其餘依賴者都會收到通知。this
設計原則spa
爲了交互對象之間的鬆耦合設計而努力。
如今,使用觀察者模式設計這個需求,類圖以下:
Subject爲主題接口,空氣質量監測站實現了主題接口。Observer爲觀察者接口,由要發佈空氣質量信息的網站所實現,同時也爲它們創建了一個顯示空氣信息的接口。代碼以下:
package cn.net.bysoft.observer; // 主題接口。 public interface Subject { // 定義註冊或刪除觀察者的方法。 public void registerObserver(Observer o); public void removeObserver(Observer o); // 當主題狀態改變時,這個方法會被調用,以通知全部的觀察者。 public void notifyObservers(); }
package cn.net.bysoft.observer; import java.util.ArrayList; import java.util.List; // 空氣質量檢測站。 public class AirQualityStations implements Subject { public AirQualityStations() { this.observers = new ArrayList<Observer>(); } // 添加觀察者。 public void registerObserver(Observer o) { observers.add(o); } // 刪除觀察者。 public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } // 當空氣質量值改變時該函數會被調用。 public void notifyObservers() { for (Observer o : observers) { // 更新觀察者的數據。 o.update(this.pollutants); } } // 空氣質量數據更新。 // 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。 public void AirQualityChanged() { notifyObservers(); } public Pollutants getPollutants() { return pollutants; } public void setPollutants(Pollutants pollutants) { this.pollutants = pollutants; } // 污染物對象。 private Pollutants pollutants; // 觀察者集合。 List<Observer> observers; }
package cn.net.bysoft.observer; // 觀察者接口。 public interface Observer { // 當空氣質量值改變時,主題會把這些狀態值看成方法的參數,傳送給觀察者。 public void update(Pollutants pollutants); }
package cn.net.bysoft.observer; // 顯示空氣質量接口。 public interface DisplayElement { public void display(); }
package cn.net.bysoft.observer; // 污染物實體類。 public class Pollutants { // 細顆粒物。 private int PM2d5; // 二氧化硫。 private int SO2; // 二氧化氮。 private int NO2; // 臭氧。 private int O3; // 一氧化碳。 private int CO; // 可吸入顆粒物。 private int PM10; public int getPM2d5() { return PM2d5; } public void setPM2_5(int pM2d5) { PM2d5 = pM2d5; } public int getSO2() { return SO2; } public void setSO2(int sO2) { SO2 = sO2; } public int getNO2() { return NO2; } public void setNO2(int nO2) { NO2 = nO2; } public int getO3() { return O3; } public void setO3(int o3) { O3 = o3; } public int getCO() { return CO; } public void setCO(int cO) { CO = cO; } public int getPM10() { return PM10; } public void setPM10(int pM10) { PM10 = pM10; } }
package cn.net.bysoft.observer; import java.util.Date; // PM2.5佈告板。 public class PM2d5Display implements Observer, DisplayElement { // 污染物。 private Pollutants pollutants; // 顯示污染物。 public void display() { System.out.println(new Date().toString()); System.out.println("PM2.5的指數是" + pollutants.getPM2d5()); System.out.println("來自 [www.哈哈哈網.com] "); System.out.println("========================================================"); } public void update(Pollutants pollutants) { // TODO Auto-generated method stub this.pollutants = pollutants; this.display(); } }
package cn.net.bysoft.observer; import java.util.Date; // 空氣質量佈告板。 public class AQIDisplay implements Observer, DisplayElement { // 污染物。 private Pollutants pollutants; public Pollutants getPollutants() { return pollutants; } public void setPollutants(Pollutants pollutants) { this.pollutants = pollutants; } public void display() { System.out.println(new Date().toString()); System.out.println("通過AQI算法最後得出空氣質量爲:" + getAQI()); System.out.println("來自 [www.嘿嘿嘿網.com] 的空氣指數數據"); System.out.println("======================================================"); System.out.println(); } private String getAQI() { int num = this.pollutants.getPM2d5(); String result = ""; if (num < 25) { result = "優"; } else if (num >= 25 && num < 50) { result = "良"; } else if (num >= 50 && num < 57) { result = "輕度污染"; } else result = "重度污染"; return result; } public void update(Pollutants pollutants) { this.pollutants = pollutants; this.display(); } }
編寫好這些類與接口後,進行測試,在建立2個類,一個是空氣質量監測儀(AirQualityApparatus),一個是空氣質量監測服務(AirQualityService,帶main函數),代碼以下:
package cn.net.bysoft.observer; import java.util.Random; // 空氣設備監測儀。 public class AirQualityApparatus { public AirQualityApparatus() {} public AirQualityApparatus(AirQualityStations airQualityStations) { this.airQualityStations = airQualityStations; } public void Start() { // 設置空氣質量。 Pollutants pollutants = new Pollutants(); pollutants.setPM2_5(getRandomNum()); pollutants.setSO2(getRandomNum()); pollutants.setNO2(getRandomNum()); pollutants.setO3(getRandomNum()); pollutants.setCO(getRandomNum()); pollutants.setPM10(getRandomNum()); airQualityStations.setPollutants(pollutants); airQualityStations.AirQualityChanged(); } // 隨機生成1-100。 private int getRandomNum() { Random r = new Random(); int num = r.nextInt(100); return num + 1; } private AirQualityStations airQualityStations; }
package cn.net.bysoft.observer; // 空氣質量服務中心。 public class AirQualityService { public static void main(String[] args) { // 創建一個空氣質量站。 AirQualityStations airQualityStations = new AirQualityStations(); // 創建一個空氣質量監測設備。 AirQualityApparatus airQualityApparatus = new AirQualityApparatus(airQualityStations); // 添加須要空氣質量數據的網站。 airQualityStations.registerObserver(new PM2d5Display()); airQualityStations.registerObserver(new AQIDisplay()); while(true) { airQualityApparatus.Start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
運行結果如圖:
Java內置了觀察者模式,與上面自定義的觀察者模式有一些小差別。主題爲java.util.Observable類,觀察者爲java.util.Observer接口。Observer接口只聲明瞭一個方法,即爲void update(Observable o, Object arg)。而Observable類則與上面的Subject有些不一樣。
Observable類多出了一個setChanged()方法,用來標記觀察的目標狀態已經改變,好讓notifyObservers()方法知道當它被調用時應該更新觀察者。由於有時不須要某種數據只要改變就會發送消息給觀察者,好比溫度,咱們但願溫度上升一度在將消息發送給觀察者,而不是上升0.1度就發送。具體能夠看Observable類的源碼:
package java.util; public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
使用Java內置的觀察者修改一下上面的空氣質量監測站,代碼與測試結果以下:
package cn.net.bysoft.observer; import java.util.Observable; // 空氣質量監測站類,繼承了java內置的可觀察者類。 public class AirQualityStations extends Observable { // 空氣質量數據更新。 // 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。 public void AirQualityChanged() { super.setChanged(); // 沒有將數據傳遞給觀察者,觀察者必須本身到這個類中拉數據。 super.notifyObservers(); } public Pollutants getPollutants() { return pollutants; } public void setPollutants(Pollutants pollutants) { this.pollutants = pollutants; } // 污染物對象。 private Pollutants pollutants; }
package cn.net.bysoft.observer; import java.util.Date; import java.util.Observable; import java.util.Observer; // PM2.5佈告板。 public class PM2d5Display implements Observer, DisplayElement { // 污染物。 private Pollutants pollutants; // 顯示污染物。 public void display() { System.out.println(new Date().toString()); System.out.println("PM2.5的指數是" + pollutants.getPM2d5()); System.out.println("來自 [www.哈哈哈網.com] "); System.out.println("========================================================"); } /*public void update(Pollutants pollutants) { // TODO Auto-generated method stub this.pollutants = pollutants; this.display(); }*/ // 實現java內置的觀察者接口的update方法。 public void update(Observable o, Object arg) { // 得到被觀察者類,也就是空氣監測站。 AirQualityStations airQualityStations = (AirQualityStations)o; this.pollutants = airQualityStations.getPollutants(); this.display(); } }
package cn.net.bysoft.observer; // 空氣質量服務中心。 public class AirQualityService { public static void main(String[] args) { // 創建一個空氣質量站。 AirQualityStations airQualityStations = new AirQualityStations(); // 創建一個空氣質量監測設備。 AirQualityApparatus airQualityApparatus = new AirQualityApparatus( airQualityStations); // 添加須要空氣質量數據的網站。 airQualityStations.addObserver(new PM2d5Display()); airQualityStations.addObserver(new AQIDisplay()); while (true) { airQualityApparatus.Start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
若是你想"推"(push)數據給觀察者,你能夠把數據看成數據對象傳送給notifyObservers(arg)方法,不然,觀察者就必須從可觀察者對象中"拉"(pull)數據,就像上訴代碼中的作法。注意,文字輸出的次序與以前的測試也不同了,由於咱們實現的是Observable類的notifyObservers()方法,這致使了通知觀察者的次序不一樣與以前的次序。
最後,java內置的Observable也有黑暗面,首先它是一個類,而不是接口,而且它也沒有去實現任何一個接口。因此咱們必須設計一個類去繼承它。若是這個類想作被java內置的觀察者類的同時又想繼承某個父類,就會陷入兩難,畢竟Java不支持多繼承。這限制了Observable類的複用潛力。再者,由於Observable沒有接口,看Observable源碼中setChanged()方法被定義爲protected。這意味着除非你繼承自Observable,不然沒法建立Observable實例被組合到本身的對象中來。因此用哪一種方式合適須要斟酌,其實本身編寫觀察者模式也沒問題,畢竟它那麼簡單。