有趣的事情发生时,可千万别错过了!java
观察者模式是JDK中使用最多的模式之一,很是有用。咱们也会一并介绍一对多关系,以及松耦合。有个观察者,你将会消息灵通。编程
工做合约设计模式
某软件公司接到一个工做合约,内容以下:函数
恭喜贵公司获选为敝公司(Weather-O-Rama气象站)创建下一代Internate气象站!测试
该气象站必须创建在咱们专利申请中的WeatherData对象上,由WeatherData负责监测目前的天气状况(温度、湿度和睦压等)。咱们但愿贵公司创建一个应用,有三种布告板,分别显示目前的状况,气象统计及简单的预报。当WeatherData对象得到最新的监测数据时,三种布告板实时更新。this
并且,这是一个能够拓展的气象站,Weather-O-Rama气象站但愿发布一组API,好让其余开发人员能够写出本身的气象布告板,并插入次应用中。咱们但愿贵公司提供这样的API。spa
气象监测站应用的概况.net
此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(最终来自气象站的数据)和布告板(显示目前天气情况给用户看)。如图:设计
WeatherData对象知道如何跟物理气象站联系,以得到监控的实施气象数据。WeatherData对象会随即更新三个布告板的显示:目前情况(温度、湿度、气压)、气象统计和天气预报。code
咱们的工做就是创建一个应用,利用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<Observer>(); } /** * 注册观察者。 * */ public void registerObserver(Observer ob) { observers.add(ob); } /** * 移除观察者。 * */ public void removeObserver(Observer ob) { int index = observers.indexOf(ob); if (index >= 0) observers.remove(index); } /** * 当有最新的气象监测数据时被调用。↓下面的函数调用。 * */ public void notifyObservers() { for (Observer ob : observers) { ob.update(this.temperature, this.himidity, this.pressure); } } /** * 气象站硬件监测到新数据会调用该方法。↓下面的函数调用。 * */ public void measurementsChanged() { notifyObservers(); } /** * 由于没有气象站,因此模拟一个硬件,得到气象数据调用measurementsChanged()方法。 * 在main()函数中使用。 * */ public void setMeasurements(float temperature, float himidity, float pressure) { this.temperature = temperature; this.himidity = himidity; this.pressure = pressure; measurementsChanged(); } public ArrayList<Observer> getObservers() { return observers; } public void setObservers(ArrayList<Observer> observers) { this.observers = observers; } public float getTemperature() { return temperature; } public void setTemperature(float temperature) { this.temperature = temperature; } public float getHimidity() { return himidity; } public void setHimidity(float himidity) { this.himidity = himidity; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } private ArrayList<Observer> observers; private float temperature; private float himidity; private float pressure; }
最后,编写布告板吧!
咱们已经把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设计模式这本书,这本书真的很不错。
以上就是观察者模式。