《Head First设计模式》批注系列(一)——观察者设计模式
最近在读《Head First设计模式》一书,此系列会引用源书内容,但文章内容会更加直接,以及加入一些自己的理解。
观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
引子
我们的公司刚刚你拿下了一个中国气象局的一个招标,负责建立北京市的气象观测站。该气象站必须建立在中国气象局专利申请中的WeatherData对象上,有WeatherData对象负责追踪目前的天气状况,(由于本文只做例子,只追踪温度、湿度、气压)。气象站提出应用的要求:
1、我们公司能建立一个应用,有三种布告板,可以显示实时天气状况、气象统计、以及简单的预报。
2、当WeatherData对象获得最新的测量数据时,三种布告板必须实时更新。
3、我们的气象站是可以扩展的,气象站希望可以公布一组API(接口),好让其他开发人员可以自己写出自己的气象公告板,并插入此应用
初步设计
1、首先根据气象站的需求,我们画一个示意图。
WeatherData对象知道如何跟物理气象站联系,当数据跟新时,WeatherData对象从气象站获得数据,WeatherData对象随机更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报,所以我们目前的主要任务是,利用WeatherData对象从气象站获取数据,并跟新三个布告板。
在设计之前,我们要看一看气象站给我们WeatherData类(此处只做示意,代码后面会变得完整)
public class WeatherData { //下面的三个方法获取温度、湿度、以及压力 public float getTemperature(){ temperature=获取温度; return temperature; } public float getHumidity(){ temperature=获取湿度; return humidity; } public float getPressure(){ temperature=获取压力; return pressure; } //一旦气象测量更新、此方法就会被调用 public void measurementsChanged(){ }
好了,到此为止我们已经知道了最终的设计要求以及气象站给我们的WeatherData类,下面我们就要开始设计我们的应用,根据我们知道的东西,我们可以先来一个不完整的初版,我们现在WeatherData中增加一些方法(不着急先写出方法的具体内容,只要写出一个方法名,记得它的功能即可)
public class WeatherData {
//下面的三个方法获取温度、湿度、以及压力 public float getTemperature(){ temperature=获取温度; return temperature; } public float getHumidity(){ temperature=获取湿度; return humidity; } public float getPressure(){ temperature=获取压力; return pressure; } //一旦气象测量更新、此方法就会被调用 public void measurementsChanged(){ float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure();
currentConditionsDisplay.update(temp,humidity,pressure);
statisticsDiaplay.updata(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure); }
这是我们第一次设计的程序,由于我们已经开始准备用设计模式了,所以我们可以看看我们目前的代码中是不是有还可以改进的地方,首先,我们看到了三个公告板都有update方法,那我们就可以把这个方法
抽取成一个接口,这样我们每次增加公告板时直接实现此接口就可以,其次,本次我们讨论的是观察者模式,那我们的引子就先说到这,我们开始看看什么是观察者设计模式,我们了解完观察者设计模式后,我们
再回到我们的引子,完成设计。
认识观察者设计模式
在生活中,订阅报纸就是就是一个观察者设计模式,报社的业务就是出版报纸,当你订阅了某家报社的报纸,只要报社有了新的报纸出版,他们就会给你送来,只要你是他们的订户,你就一定会收到报
纸,当你不想在看这家报纸时,你就可以取消订阅,他们就不会再送来新报纸。其实,出版社+订阅者就等于观察者模式,只是在这个模式里,出版社叫做“主题(Subject)”,订阅者叫做观察者"(Observer)”,如果
你还没有看懂,那我们来看下面这个图:
图中主题对象管理者int数据,狗对象、猫对象、老鼠对象订阅了主题,一旦主题对象发生改变,就会给这些订阅了的对象发送数据,鸭子对象没有订阅主题,就不会接收到数据。
有一天,鸭子对象突然对主题对象管理的数据起了兴趣,它就需要过去告诉主题,我对你的数据改变有兴趣,一有变化请告诉我,这就是“订阅”的过程,在这之后,鸭子对象就是正式
的观察者了,以后主题管理的数据再有改变就会告诉鸭子对象。
又有一天,老鼠对象对主题说,你管理的数据我观察太久了,我不想再当你的观察者了,这时主题就删掉老鼠对象,每当数据再变化时,老鼠对象就不会接收到数据。
观察者设计模式定义了对象之间的一对多依赖,这样的依赖,当一个对象改变时,它的所有依赖者都会受到通知并自动更新
下面是便是定义观察者设计模式的类图:
将观察者设计模式应用到我们的气象站中
我们已经基本了解了观察者设计模式,那我们就可以回到我们的引子完成原来的设计
初步思考
通过我们在上面分析的观察者设计模式,我们发现这是模式是一对多的关系,每当一改变时,就会给多发送改变的数据,而在我们的气象站设计中,正是一对多的关系,一就是我们的WeatherData数据
多就是显示的布告板,而不同的布告板有相同和不同的方法,我们可以把相同的部分抽取成接口,实现代码的复用,那么下面就让我们参照上面观察者设计模式的类图画一下我们气象站的设计类图。(自己
先尝试一下哟)
设计气象站
实现气象站
根据上面的类图,我们就可以完善我们的代码啦
Subject类的接口及WeatherData实现
package com.frank.observe.subject; import com.frank.observe.observer.Observer; public interface Subject {
//主题注册、删除、通知观察者方法 public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObserver(); //信息改变时执行方法 public void measurementsChanged(); }
package com.frank.observe.subject; import java.util.ArrayList; import com.frank.observe.observer.Observer; public class WeatherData implements Subject {
//WeatherData实现了Subject方法 private ArrayList<Observer> observers;
//包含温度、湿度、压力 private float temperature; private float humidity; private float pressure; //构造器创建观察者们的集合 public WeatherData(){ observers = new ArrayList<Observer>(); } //接口方法 @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(o); } } @Override public void notifyObserver() { for(int i = 0;i<observers.size();i++){ Observer observer = observers.get(i); observer.update(temperature, humidity, pressure); } } @Override public void measurementsChanged() { notifyObserver(); } //WeatherData自己的set方法,一会测试时在test中改变数据源 public void setMeasurements(float temperature,float humidity, float pressure){ this.temperature=temperature; this.humidity=humidity; this.pressure=pressure; measurementsChanged(); } }
Observer类的接口以及CurrentConditionDisplay实现
package com.frank.observe.observer; //Observer接口中更新数据的方法 public interface Observer { public void update(float temp,float humidity,float pressure); }
package com.frank.observe.observer; import com.frank.observe.display.DisplayElement; import com.frank.observe.subject.Subject; import com.frank.observe.subject.WeatherData; //显示板要实现Observer以及DisplayElement接口(及公共方法) public class CurrentConditionDisplay implements Observer, DisplayElement { private float temprature; private float humidity;
//将weatherData作为自己的成员变量 private Subject weatherData; //在带参构造器中将自己注册到weatherData中 public CurrentConditionDisplay(Subject weatherData){ this.weatherData=weatherData; weatherData.registerObserver(this); } //接口的更新方法 @Override public void update(float temp, float humidity, float pressure) { this.temprature=temp; this.humidity=humidity;
//接口的显示方法 display(); } @Override public void display() { System.out.println("current conditions" +temprature+"F degrees and"+humidity+"% humidity"); } }
DisplayElement接口
package com.frank.observe.display; public interface DisplayElement { public void display(); }
Test方法
package com.frank.observe.test; import com.frank.observe.observer.CurrentConditionDisplay; import com.frank.observe.subject.WeatherData; public class Test { public static void main(String[] args) {
//新建一个主题 WeatherData weatherData = new WeatherData();
//新建一个公告板并将自己注册到主题中 CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData);
//每当主题更新数据时发送给观察者,观察者显示在公告板中 weatherData.setMeasurements(80, 60, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); } }
结果
current conditions80.0F degrees and60.0% humidity current conditions82.0F degrees and70.0% humidity current conditions78.0F degrees and90.0% humidity
总结
要点:
OO基础:抽象
OO原则:封装变化、多用组合,少用继承、针对接口编程,不针对实现编程
OO模式:观察者设计模式----在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知,并自动更新(后面的系列会看到其代表人物--MVC)