背景問題:假設我們現在接到一個來自氣象臺的軟件開發合約,對方希望我們開發一個天氣預報類的軟件。假設需要建立三種佈告板,分別顯示目前的狀況,氣象統計及簡單的預報。當氣象臺提供的WeatherObject對象獲得最新的測量數據時,三種佈告板必須實時更新。如氣象臺所說,他們的WeatherData源文件中有getTemperature( ),getHumidity( ),getPressure( ),measurementsChanged( )。對於前三個方法各自返回最近的氣象測量數據,我們不用在乎這些方法如何設置變量,WeatherData對象自己知道如何從氣象臺獲取更新。而最後一個方法作爲一個線索提示我們需要在這裏添加代碼,顧名思義一旦三個變量更新,這個方法會被調用。
順水推舟:根據氣象臺的暗示,在measurementsChanged( )方法中添加我們的代碼貌似是個不錯的選擇:
- public class WeatherData{
- public void measurementsChanged(){
- float temp = getTemperature();
- float humidity = getHumidity();
- float pressure = getPressure();
- currentConditionsDisplay.update(temp,humidity,pressure);
- statisticsDisplay.update(temp,humidity,pressure);
- forecastDisplay.update(temp,humidity,pressure);
- ]
- }
思考:哪裏出了問題?回想我們在設計模式1博文中討論過的設計原則,顯而易見
- currentConditionsDisplay.update(temp,humidity,pressure);
- statisticsDisplay.update(temp,humidity,pressure);
- forecastDisplay.update(temp,humidity,pressure);
是針對具體實現在編程,這樣會導致我們以後再增加或刪除佈告板時必須修改程序,而至少,update()在這裏看起來像一個統一的接口,參數都是溫度,溼度,氣壓。
這也就引出了我們博客討論的第三個設計原則:爲了交互對象之間的鬆耦合設計而努力。鬆耦合有利於我們建立有彈性的系統,能夠應對變化,是因爲對象之間的互相依賴降低到了最低。這符合我們這篇博客所論述的主題:觀察者模式。
炒冷飯:觀察者模式定義了對象之間的一對多依賴,這樣一來,對一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。當思考這個定義時,我們會發現很有道理。我們的WeatherData類正是此處所說的“一”,而我們的“多”正是此處使用的天氣預測的各種佈告板。我們還必須記得,每個佈告板都有差異,這也就是爲什麼我們需要一個共同的接口的原因。儘管佈告板的類不一樣,但是它們都應該實現相同的接口,好讓WeatherData對象能夠知道如何把觀測值送給它們。顯而易見:update()方法應該在所有佈告板都實現的共同接口裏定義。
解決方案:
方案一:不使用JAVA爲觀察者模式提供的內置支持(理由:自己動手豐衣足食,自己建立的一切都會更具有彈性,況且建立這一切並不是很麻煩)
1.接口部分
a.主題接口(Subject.java)
- public interface Subject {
- public void registerObserver(Observer o);
- public void removeObserver(Observer o);
- public void notifyObserver();
- }
b.觀察者接口(Observer.java)
- public interface Observer {
- public void update(float temp,float humidity,float pressure);
- }
c.顯示接口(DisplayElement)
- public interface DisplayElement {
- public void display();
- }
2.實現部分
a.主題實現(WeatherData.java)
- import java.util.*;
- public class WeatherData implements Subject{ //WeatherData現在實現了Subject接口
- private ArrayList observers;//加上了ArrayList來記錄觀察者,此ArrayList是在構造函數中建立的
- private float temperature;
- private float humidity;
- private float pressure;
- public WeatherData(){
- observers=new ArrayList();
- }
- public void registerObserver(Observer o){
- observers.add(o);//當註冊觀察者時,我們只要把它加到ArrayList的後面即可
- }
- public void removeObserver(Observer o){
- int i=observers.indexOf(o);//同樣地,當觀察者想取消註冊,我們把它從ArrayList中刪除即可
- if(i>0){
- observers.remove(i);
- }
- }
- public void notifyObserver(){//我們把狀態告訴每個觀察者,因爲觀察者都實現了update(),所以我們知道如何通知它們
- for(int i=0;i<observers.size();i++){
- Observer observer=(Observer)observers.get(i);
- observer.update(temperature,humidity,pressure);
- }
- }
- public void measurementsChanged(){
- notifyObserver();//當從氣象站得到更新觀測值時,我們通知觀察者
- }
- public void setMeasurements(float temperature,float humidity,float pressure){
- this.temperature=temperature;//測試佈告板,也可以寫代碼從網站上抓取觀測值
- this.humidity=humidity;
- this.pressure=pressure;
- measurementsChanged();
- }
- public float getTemperature(){
- return temperature;
- }
- public float getHumidity(){
- return humidity;
- }
- public float getPressure(){
- return pressure;
- }
- }
b.觀察者即佈告板的實現(CurrentConditionsDisplay.java)
- public class CurrentConditionsDisplay implements Observer,DisplayElement{//實現了Observer接口,所以可以從WeatherData對象中獲得改變
- private float temperature;
- private float humidity;
- private Subject weatherData;
- public CurrentConditionsDisplay(Subject weatherData){//構造器需要weatherData對象(也就是主題)作爲註冊之用
- this.weatherData=weatherData;
- weatherData.registerObserver(this);
- }
- public void update(float temperature,float humidity,float pressure){
- this.temperature=temperature;//當update()被調用時,我們把溫度和溼度保存起來,然後調用display()
- this.humidity=humidity;
- display();
- }
- public void display(){
- System.out.println("Current conditions:"+temperature+"F degrees and "+humidity+"%humidity");
- }
- }
3.測試程序(WeatherStation.java)
- import java.util.*;
- public class WeatherStation {
- public static void main(String[] args) {
- WeatherData weatherData = new WeatherData();
- CurrentConditionsDisplay currentDisplay =
- new CurrentConditionsDisplay(weatherData);
- //StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
- //ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
- weatherData.setMeasurements(80, 65, 30.4f);
- weatherData.setMeasurements(82, 70, 29.2f);
- weatherData.setMeasurements(78, 90, 29.2f);
- }
- }
4.測試界面