背景问题:假设我们现在接到一个来自气象台的软件开发合约,对方希望我们开发一个天气预报类的软件。假设需要建立三种布告板,分别显示目前的状况,气象统计及简单的预报。当气象台提供的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
- 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.测试界面