此系统的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。
具体来说该应用需要:利用WeatherDate对象从气象站取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新。
主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。
关于观察者的一切,主题只知道观察者实现了某个接口(Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节,将对象之间的相互依赖性降到最低。符合了
设计原则:
为了交互对象之间的松耦合设计和努力。
结合气象站的需求和观察者模式的定义,得到气象站的设计图如下:
从图中可以看出,有三个接口需要建立:Subject、Observer、DisplayElement
Subject:
package com.lissdy; public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObserver(); }
Observer:
package com.lissdy; public interface Observer { public void update(float temp,float humidity,float pressure); }
DisplayElement:
package com.lissdy; public interface DisplayElement { public void display(); }
在WeatherData中实现Subject接口:
package com.lissdy; import java.util.ArrayList; public class WeatherData implements Subject { private ArrayList observers; private float temperature; private float pressure; private float humidity; public WeatherData() { observers = new ArrayList(); //加上一个ArrayList来记录观察者,此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() { 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(); } }
建立当前天气状况和温度统计的布告板:
当前天气状况:
package com.lissdy; public class CurrentDisplay implements Observer,DisplayElement{ private float temperature; private float humidity; private Subject weatherDate; public CurrentDisplay(Subject weatherDate) { this.weatherDate=weatherDate; weatherDate.registerObserver(this); } public void update(float temperature,float humidity,float pressure) { this.temperature=temperature; this.humidity=humidity; display(); } public void display() { System.out.println("目前状况是温度:"+temperature+"度 "+"湿度:"+humidity+"%"); } }
温度统计:
package com.lissdy; public class StatisticsDisplay implements Observer,DisplayElement{ private float temperature; private Subject weatherDate; private float max=0; private float min=100; private float sum=0; private int i=0; public StatisticsDisplay(Subject weatherDate) { this.weatherDate=weatherDate; weatherDate.registerObserver(this); } public void update(float temperature,float humidity,float pressure) { this.temperature=temperature; i++; sum=sum+temperature; if(temperature>max) { max=temperature; } if(temperature<min) { min=temperature; } display(); } public void display() { System.out.println("平均温度是:"+(sum/i)+"最高温度是:"+max+"最低温度是:"+min); } }
建立一个测试程序:
package com.lissdy; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData=new WeatherData(); //建立一个WeatherData对象 CurrentDisplay currentDisplay=new CurrentDisplay(weatherData); //建立布告板,并把WeatherData传给它们 StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); //模拟新的气象测量 weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } }
执行结果:
以上采用自己构建观察者模式的方法完成了气象站系统。但是,JAVA API内有内置的观察者模式。
java .util包内包含了最基本的Observer接口和Observable类,这和之前自己构造的Observer接口和Subject接口很相似。
若使用java内置观察者模式实现气象站系统,其设计图为:
利用内置的支持重做气象站:
首先,把WeatherData改成使用java.util.Observable
package com.lissdy; import java.util.Observable; public class WeatherData extends Observable { private float temperature; private float pressure; private float humidity; public WeatherData() { } // 不需要再使用ArrayList来记录观察者了,API代劳 public void measurementsChanged() { setChanged(); notifyObservers(); } 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; } }
重做布告板:
当前天气:
package com.lissdy; import java.util.Observer; import java.util.Observable; public class CurrentDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float humidity; public CurrentDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable obs, Object arg) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData) obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); display(); } } public void display() { System.out.println("目前状况是温度:" + temperature + "度 " + "湿度:" + humidity + "%"); } }
气温统计:
package com.lissdy; import java.util.Observable; import java.util.Observer; public class StatisticsDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float max = 0; private float min = 100; private float sum = 0; private int i = 0; public StatisticsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable obs, Object args) { if (obs instanceof WeatherData) { WeatherData weatherData = (WeatherData) obs; this.temperature = weatherData.getTemperature(); i++; sum = sum + temperature; if (temperature > max) { max = temperature; } if (temperature < min) { min = temperature; } display(); } } public void display() { System.out.println("平均温度是:" + (sum / i) + "最高温度是:" + max + "最低温度是:" + min); } }
执行结果:
注意和之前得到的结果相同,但是布告板的排列顺序不同。这是由于自己实现的观察者模式和JAVA API中的notifyObservers()方法实现方式不同造成的。
java.util.Observable是一个类而不是一个接口,违反了针对接口编程,而非针对实现编程的设计原则。
在JavaBeans和Swing中也都实现了观察者模式,例如一个按钮绑定两个监听器,按下按钮时,两个监听器都被触发。