设计模式学习之二观察者模式(Observer)——参与气象观测站的设计

业务需求:希望建立下一代Internet气象观测站!该气象站必须建立在我们专利申请中的WeatherData对象上,WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。要求:建立一个应用,有三种布告板,分别显示目前的状况,气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。而且,这是一个可以扩展的气象站,Weather-O-Rama气象站希望能有一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。接下来看WeatherDate类设计模式学习之二观察者模式(Observer)——参与气象观测站的设计

先看一个错误的示范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); } //这里是其他的WeatherData方法 }

我们的实现有什么不对呢,仔细想想oo的设计原则就知道了

currentConditionsDisplay.update(temp, humidity, pressure);

statisticsDisplay.update(temp, humidity, pressure);

forecastDisplay.update(temp, humidity, pressure);

这里针对了具体实现编程,会使我们在增加或删除布告板时必须修改程序,而且布告板的方法都是update,至少这里看起来应该像是一个统一的接口。改变的地方需要封装滴。。。。

那么先让我们认识一下观察者模式吧,报纸订阅的方式估计大家都能想明白,如果你明白报纸订阅是怎么回事,那么恭喜你,你已经明白观察者模式是怎么回事了。 出版者+订阅者=观察者模式,把名称改一下吧,改的专业一点,出版者改成 主题(Subject), 订阅之改成 观察者(Observer),改后 主题(Subject)+观察者(Observer)=观察者模式(Observer),那么根据报纸的订阅方式可以给观察者模式下定义了:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。稍后你会看到实现观察者模式的方法不止一种,但是以包含Subject和Observer接口的类设计的做法最常见。下面是观察者模式的类图,直接截的图:

设计模式学习之二观察者模式(Observer)——参与气象观测站的设计

观察者模式提供了一种对象设计,让主题和观察者之间松耦合(解耦合),我们可以独立的复用主题或观察者,因为二者并非紧耦合,只要他们之间的接口仍被遵守,我们就可以*的改变他们。由此,我们又碰到一个设计原则:为了交互对象之间的松耦合(解耦合)设计而努力。OK 看过观察者模式了,那么开始设计气象站吧,设计图如下:设计模式学习之二观察者模式(Observer)——参与气象观测站的设计

根据上面的类图,来实现气象站吧,先从建立接口开始,代码如下:

public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); //当主题变化时这个方法会被调用,通知所有的观察者 public void notifyObservers(); }public interface Observer { public void update(float temperature, float humidity, float pressure); }public interface DisplayElement { //当布告板需要显示时调用此方法 public void display(); }

继续在WeatherData中实现主题接口

:import java.util.ArrayList; public class WeatherData implements Subject { private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if(i>=0) { observers.remove(i); } } @Override public void notifyObservers() { for(int i=0;i<observers.size();i++) { Observer observer = (Observer)observers.get(i); observer.update(temperature, humidity, pressure); } } public void measurementsChanged() { notifyObservers(); } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } //这里是其他的WeatherData方法 }

接下来建立布告板:

public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } @Override public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "%humidity"); } }

最后可以建立一个测试程序来看我们的劳动成果了:

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); } }

到此,气象站算初步完成了。但是 Java API 有内置的观察者模式。java.util包内包含最基本的Observer接口与Observable类,这和我们的Subject接口与Observer接口很类似,Observer接口与Observable类使用起来更方便,因为许多功能都已经事先准备好了,上面我们自己实现的只是使用推(push)传送数据,但在内置的模式中可以使用推(push)或拉(pull)的方式传送数据。使用内置的观察者模式重新设计气象站:

设计模式学习之二观察者模式(Observer)——参与气象观测站的设计

我们再做一次气象站,但这次用拉(pull)来传送数据。首先,把WeatherDate改成使用java.util.Observable:

import java.util.Observable; public class WeatherData extends Observable { private float temperature; private float humidity; private float pressure; public WeatherData() { } public void measurementsChanged() { //在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变 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; } }

现在也让我们重做CurrentConditionsDisplay:

import java.util.Observable; import java.util.Observer; public class CurrentConditionsDisplay implements Observer, DisplayElement { Observable observable; private float temperature; private float humidity; public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); } @Override public void update(Observable obs, Object arg) { if(obs instanceof WeatherData) { WeatherData weatherData = (WeatherData)obs; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); display(); } } @Override public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "%humidity"); } }

运行WeatherStation.java再次进行测试。。。。不知你注意到了吗,可观察者是一个“类”而不是一个“接口“,违反了针对接口编程,而非针对实现编程。如果你看看Observable的API,你会发现setChanged()方法被保护起来了(被定义成了protected),这意味着:除非你继承自Observable,否则无法创建Observable实例并组合到你自己的对象中来。这个设计违反了:多用组合,少用继承。但是当你熟悉观察者模式之后这一切都不是问题,你可以自己实现的,选择权在于你。其实,并非只有在java.util中才能找到观察者模式,其实在JavaBeans和Swing中,也都实现了观察者模式。