有趣的事情發生時,可千萬別錯過了!java
觀察者模式是JDK中使用最多的模式之一,很是有用。咱們也會一併介紹一對多關係,以及鬆耦合。有個觀察者,你將會消息靈通。編程
工做合約設計模式
某軟件公司接到一個工做合約,內容以下:函數
恭喜貴公司獲選爲敝公司(Weather-O-Rama氣象站)創建下一代Internate氣象站!測試
該氣象站必須創建在咱們專利申請中的WeatherData對象上,由WeatherData負責監測目前的天氣狀況(溫度、溼度和睦壓等)。咱們但願貴公司創建一個應用,有三種佈告板,分別顯示目前的狀況,氣象統計及簡單的預報。當WeatherData對象得到最新的監測數據時,三種佈告板實時更新。this
並且,這是一個能夠拓展的氣象站,Weather-O-Rama氣象站但願發佈一組API,好讓其餘開發人員能夠寫出本身的氣象佈告板,並插入次應用中。咱們但願貴公司提供這樣的API。spa
氣象監測站應用的概況.net
此係統中的三個部分是氣象站(獲取實際氣象數據的物理裝置)、WeatherData對象(最終來自氣象站的數據)和佈告板(顯示目前天氣情況給用戶看)。如圖:設計
WeatherData對象知道如何跟物理氣象站聯繫,以得到監控的實施氣象數據。WeatherData對象會隨即更新三個佈告板的顯示:目前情況(溫度、溼度、氣壓)、氣象統計和天氣預報。code
咱們的工做就是創建一個應用,利用WeatherData對象得到數據,並更新三個佈告板。
瞧一瞧客戶給提供的WeatherData類吧。
咱們的工做是實現measurementsChanged()方法,好讓它更新目前的情況、氣象統計和天氣預報的顯示佈告板。
咱們目前知道什麼?
Weather-O-Rama氣象站的要求說明並非很清楚,咱們必須搞懂該作些什麼,那麼,咱們目前知道些什麼呢?
WeatherData類具備getter方法,能夠取得三個測量值:溫度、溼度與氣壓。
當新的測量數據準備好時,measurementsChanged()方法就會被調用(咱們不在意次方法是如何被調用的,咱們只在意它被調用了,也許被某個其餘的程序調用的,好比硬件採集器,這是Weather-O-Rama氣象站提供的WeatherData類已經實現的功能)。
咱們須要實現三個使用天氣數據的佈告板,一旦WeatherData有新的測量數據就立刻更新。
次系統必須可拓展,讓其餘開發人員自定義佈告板。
先看一個錯誤的示範
若是不進行思考,直接進入開發階段,只爲了完成工做而工做,那麼這是第一個可能的實現。咱們依照Weather-O-Rama氣象站開發人員的暗示,在客戶提供的類的measurementsChanged()方法中添加咱們的代碼:
package cn.net.bysoft.observer; /** * 這是一個沒有通過任何思考而寫的代碼。 * */ public class WeatherData { // 該方法在硬件有最新監測數據時被調用。 public void measurementsChanged() { // 該類中的代碼又咱們公司實現。 // 調用三個getter方法得到最新的監測數據。 float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); // 更新三個咱們公司本身開發的佈告板類。 currentConditionsDispaly.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDispaly.update(temp, humidity, pressure); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } // 這三個setter方法可能會在硬件監測到最新的氣象數據時被賦值。 // 而後硬件會調用measurementsChanged()方法。 public void setTemperature(float temperature) { this.temperature = temperature; } public void setHumidity(float humidity) { this.humidity = humidity; } public void setPressure(float pressure) { this.pressure = pressure; } private float temperature; private float humidity; private float pressure; }
在咱們的第一個實現中,存在一下的缺點:
咱們是針對具體實現編程,而非針對接口;
對於每一個新的佈告版,咱們都得修改代碼;
咱們沒法在運行時動態地添加或刪除佈告板;
咱們還沒有封裝改變的部分;
咱們的實現有什麼不對?
針對具體實現編程,會致使咱們之後在添加或刪除佈告板時必須修改程序。並且,佈告板的update()方法看起來是一個統一的接口,佈告板的方法名稱都是update(),參數都是溫度、溼度和睦壓。
如今就來看看觀察者模式,而後再回來看看如何將此模式應用到氣象觀測站。
觀察者就好像訂閱報紙或者雜誌。
報社的業務就是出版報紙。
向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來。只要你是他們的訂戶,你就會一直收到新報紙。
當你不想再看報紙的時候,取消訂閱,他們就不會再送新報紙來。
只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱。
訂閱者+出版者=觀察者模式,只是名稱不太同樣。
出版者改成主題(Subject),訂閱者改成觀察者(Observer),一個主題對應多個觀察者,是一個一對多的關係。來看一下觀察者的類圖:
這裏有一個設計原則:
設計原則:
爲了交互對象之間的鬆耦合設計而努力。
鬆耦合的威力
當兩個對象之間鬆耦合,他們依然能夠交互,可是不太清楚彼此的細節。
觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。
關於觀察者的一切,主題值知道觀察者實現了某個接口。主題不須要知道觀察者的具體類是誰,作了些什麼或其餘任何細節。
任什麼時候候咱們均可以添加新的觀察者。由於主題惟一依賴的東西是一個實現Observer接口的對象列表,因此咱們能夠隨時添加觀察者。事實上,在運行時咱們能夠用新的觀察者取代現有的觀察者,主題不會受到任何影響。一樣的,也能夠在任什麼時候候刪除某些觀察者。
有新類型出現的觀察者出現時,主題的代碼不須要修改。咱們能夠獨立地複用主題或觀察者。若是咱們在其餘任何地方須要使用主題或觀察者,能夠輕易地複用,由於兩者並不是緊耦合,而是鬆耦合。
鬆耦合的設計之因此能讓咱們創建有彈性的OO系統,可以應對變化,是由於對象之間的互相依賴降到了最低。
設計與實現氣象站
瞭解了這麼多,開始設計與實現氣象站吧。
設計類圖如上圖所示,讓咱們從創建接口開始吧!
package cn.net.bysoft.observer; /** * 佈告板顯示信息接口。 * */ public interface DisplayElement { /** * 當佈告板須要顯示時,調用次方法。 * */ public void display(); }
package cn.net.bysoft.observer; /** * 觀察者接口。 * */ public interface Observer { /** * 當氣象監測數據改變時,主題會把這些狀態值看成方法的參數,傳送給觀察者。 * */ public void update(float temp, float humidity, float perssure); }
package cn.net.bysoft.observer; /** * 主題接口。 * */ public interface Subject { /** * registerObserver和removeObserver方法都須要一個觀察者做爲變量,該觀察者是用來註冊或被刪除的。 * */ public void registerObserver(Observer ob); public void removeObserver(Observer ob); /** * 當主題的狀態改變時,這個方法會被調用,已通知全部的觀察者。 * */ public void notifyObservers(); }
接下來,咱們要用觀察者模式實現WeatherData類了。
package cn.net.bysoft.observer; import java.util.ArrayList; public class WeatherData implements Subject { public WeatherData() { observers = new ArrayList<Observer>(); } /** * 註冊觀察者。 * */ public void registerObserver(Observer ob) { observers.add(ob); } /** * 移除觀察者。 * */ public void removeObserver(Observer ob) { int index = observers.indexOf(ob); if (index >= 0) observers.remove(index); } /** * 當有最新的氣象監測數據時被調用。↓下面的函數調用。 * */ public void notifyObservers() { for (Observer ob : observers) { ob.update(this.temperature, this.himidity, this.pressure); } } /** * 氣象站硬件監測到新數據會調用該方法。↓下面的函數調用。 * */ public void measurementsChanged() { notifyObservers(); } /** * 由於沒有氣象站,因此模擬一個硬件,得到氣象數據調用measurementsChanged()方法。 * 在main()函數中使用。 * */ public void setMeasurements(float temperature, float himidity, float pressure) { this.temperature = temperature; this.himidity = himidity; this.pressure = pressure; measurementsChanged(); } public ArrayList<Observer> getObservers() { return observers; } public void setObservers(ArrayList<Observer> observers) { this.observers = observers; } public float getTemperature() { return temperature; } public void setTemperature(float temperature) { this.temperature = temperature; } public float getHimidity() { return himidity; } public void setHimidity(float himidity) { this.himidity = himidity; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } private ArrayList<Observer> observers; private float temperature; private float himidity; private float pressure; }
最後,編寫佈告板吧!
咱們已經把WeatherData類寫出來了,如今輪到佈告板了。Weather-O-Rama氣象站訂購了三個佈告板,首先來實現其中一個佈告板。
package cn.net.bysoft.observer; public class CurrentConditionsDisplay implements Observer, DisplayElement { public CurrentConditionsDisplay() {} public CurrentConditionsDisplay(Subject weatherData) { // 實例化佈告板的時候,傳入一個主題,將本身註冊到主題中。 this.weatherData = weatherData; weatherData.registerObserver(this); } public void display() { System.out.println("當前conditions:" + temperature + "F degrees and " + himidity + "% humidity"); } public void update(float temp, float humidity, float perssure) { this.temperature = temp; this.himidity = humidity; // 更新數據後刷新佈告板。 display(); } private float temperature; private float himidity; // 要觀察的主題。 private Subject weatherData; }
佈告板已經實現完畢,編寫一個main()方法測試一下觀察者模式吧!
package cn.net.bysoft.observer; public class Main { public static void main(String[] args) { // 首先,建立一個主題。 // 這裏應該使用Subject weatherData = new WeatherData();來建立主題。 // 可是咱們在WeatherData中添加了一個模擬硬件發送氣象數據的函數,Subject中並無這個函數。 // 因此使用了WeatherData來建立主題。 WeatherData weatherData = new WeatherData(); // 創建一個觀察者(也就是佈告板),把主題傳給觀察者,在觀察者的構造函數中訂閱主題。 Observer ccDisplay = new CurrentConditionsDisplay(weatherData); // 模擬一個氣象變化的狀況。 weatherData.setMeasurements(80, 65, 30.4f); System.out.println("\n====================\n"); // 又變化了! weatherData.setMeasurements(80, 65, 30.4f); /** * output: * 當前conditions:80.0F degrees and 65.0% humidity * * ==================== * * 當前conditions:80.0F degrees and 65.0% humidity * * */ } }
另外兩個佈告板如上代碼,另外,Java中提供了內置的觀察者模式接口,它們是:
java.util.Observable
java.util.Observer
想要進一步瞭解能夠網上查找資料,或者和我同樣購買HeadFirst設計模式這本書,這本書真的很不錯。
以上就是觀察者模式。