为什么80%的码农都做不了架构师?>>>
有趣的事情发生时,可千万别错过了!
观察者模式是JDK中使用最多的模式之一,非常有用。我们也会一并介绍一对多关系,以及松耦合。有个观察者,你将会消息灵通。
工作合约
某软件公司接到一个工作合约,内容如下:
恭喜贵公司获选为敝公司(Weather-O-Rama气象站)建立下一代Internate气象站!
该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData负责监测目前的天气情况(温度、湿度和气压等)。我们希望贵公司建立一个应用,有三种布告板,分别显示目前的情况,气象统计及简单的预报。当WeatherData对象获得最新的监测数据时,三种布告板实时更新。
而且,这是一个可以拓展的气象站,Weather-O-Rama气象站希望发布一组API,好让其他开发人员可以写出自己的气象布告板,并插入次应用中。我们希望贵公司提供这样的API。
气象监测站应用的概况
此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(最终来自气象站的数据)和布告板(显示目前天气状况给用户看)。如图:
WeatherData对象知道如何跟物理气象站联系,以获得监控的实施气象数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。
我们的工作就是建立一个应用,利用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
}
最后,编写布告板吧!
我们已经把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设计模式这本书,这本书真的很不错。
以上就是观察者模式。