《HeadFirst设计模式》读书笔记-第2章-观察者模式
定义
观察者模式(observer pattern)定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会接收到通知并自动更新。
类图
代码实现
下面以气象站为例子,说明观察者模式的实现。先给出类图如下。
定义主题接口,所有具体的主题都要实现这个接口。
public interface Subject {// 注册/注销观察者public void registerObserver(Observer o);public void removeObserver(Observer o);// 当主题内容改变时,通知观察者public void notifyObservers();
}
定义观察者接口。
public interface Observer {public void update(float temp, float humidity, float pressure);
}
气象站作为具体的主题,实现Subject接口。
import java.util.*;public class WeatherData implements Subject {private ArrayList observers;private float temperature;private float humidity;private float pressure;public WeatherData() {observers = new ArrayList();}public void registerObserver(Observer o) {observers.add(o);}public void removeObserver(Observer o) {int i = observers.indexOf(o);if (i >= 0) {observers.remove(i);}}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();}// other WeatherData methods herepublic float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}
}
定义前端显示接口,前端显示是可能发生变化或者扩展的部分,应该独立封装。
public interface DisplayElement {public void display();
}
具体的观察者实现Observer接口。当气象站的数据改变时,主题会通知所有观察者,update()方法被调用,数据更新后显示在前端。
观察者1:显示当前的观测
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);}public void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;display();}public void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}
}
观察者2:显示天气预报
import java.util.*;public class ForecastDisplay implements Observer, DisplayElement {private float currentPressure = 29.92f; private float lastPressure;private WeatherData weatherData;public ForecastDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {lastPressure = currentPressure;currentPressure = pressure;display();}public void display() {System.out.print("Forecast: ");if (currentPressure > lastPressure) {System.out.println("Improving weather on the way!");} else if (currentPressure == lastPressure) {System.out.println("More of the same");} else if (currentPressure < lastPressure) {System.out.println("Watch out for cooler, rainy weather");}}
}
观察者3:显示酷热指数
public class HeatIndexDisplay implements Observer, DisplayElement {float heatIndex = 0.0f;private WeatherData weatherData;public HeatIndexDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float t, float rh, float pressure) {heatIndex = computeHeatIndex(t, rh);display();}// 计算酷热指数,来自气象学的公式private float computeHeatIndex(float t, float rh) {float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +0.000000000843296 * (t * t * rh * rh * rh)) -(0.0000000000481975 * (t * t * t * rh * rh * rh)));return index;}public void display() {System.out.println("Heat index is " + heatIndex);}
}
观察者4:显示温度的平均值,最大值,最小值
import java.util.*;public class StatisticsDisplay implements Observer, DisplayElement {private float maxTemp = 0.0f;private float minTemp = 200;private float tempSum= 0.0f;private int numReadings;private WeatherData weatherData;public StatisticsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {tempSum += temp;numReadings++;if (temp > maxTemp) {maxTemp = temp;}if (temp < minTemp) {minTemp = temp;}display();}public void display() {System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)+ "/" + maxTemp + "/" + minTemp);}
}
测试启动代码
import java.util.*;public class WeatherStation {public static void main(String[] args) {// new 天气主题对象WeatherData weatherData = new WeatherData();// new 观察者对象,并注册到主题对象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 API有内置的观察者模式,java.util.Observable和java.util.Observer。这两个和我们上面介绍的Subject/Observer接口很类似。我们先给出以Observable/Observer实现的新的气象站设计后,然后对比双方的优缺点。
可观察者实现。
import java.util.Observable;
import java.util.Observer;public class WeatherData extends Observable {private float temperature;private float humidity;private float pressure;public WeatherData() { }public void measurementsChanged() {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;}
}
观察者1:显示当前的观测
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);}public void update(Observable obs, Object arg) {if (obs instanceof WeatherData) {WeatherData weatherData = (WeatherData)obs;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}public void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}
}
观察者2:显示天气预报
import java.util.Observable;
import java.util.Observer;public class ForecastDisplay implements Observer, DisplayElement {private float currentPressure = 29.92f; private float lastPressure;public ForecastDisplay(Observable observable) {observable.addObserver(this);}public void update(Observable observable, Object arg) {if (observable instanceof WeatherData) {WeatherData weatherData = (WeatherData)observable;lastPressure = currentPressure;currentPressure = weatherData.getPressure();display();}}public void display() {System.out.print("Forecast: ");if (currentPressure > lastPressure) {System.out.println("Improving weather on the way!");} else if (currentPressure == lastPressure) {System.out.println("More of the same");} else if (currentPressure < lastPressure) {System.out.println("Watch out for cooler, rainy weather");}}
}
测试启动代码
public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentConditions = 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);}
}
两种观察者模式的对比:
Observable是类,Subject是接口
Observable是类的优点是子类的代码会更加简洁,子类中无需实现addObserver()/deleteObserver()/notifyObservers()等方法,因为父类已经实现了。
缺点是因为Observable是类,所以要设计一个类来继承它。如果某类想同时具有Observable和另一个超类的行为,就会陷入两难,毕竟java不支持多重继承。这限制了Observable的复用潜力。
另外,Observable中的setChanged()方法被设置为protected,这意味着,除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来(因为组合后的Observable实例无法访问setChanged()方法)。这个违反了OO原则3(多用组合,少用继承)。
Observable有增加setChanged()方法,这样可以让你在更新观察者时,更具有弹性。比如在这个案例中,你可以选择在温度变化超过多少度时,才通知观察者,这样可以避免因为非常微小的温度变化而导致的频繁通知观察者。
Observable提供了notifyObservers()和notifyObservers(Object arg)两个方法通知观察者。
如果你想push数据给观察者,你可以把数据对象传递给notifyObservers(Object arg)方法,观察者实现的update(Observable obs, Object arg)方法被调用时,数据对象会通过arg参数传递给update方法。
当调用notifyObservers()方法时,arg参数为空,这时需要观察者通过obs对象pull数据,本案例weatherData.getPressure就是pull数据的例子。
该模式体现了哪些OO原则
原则2: 针对接口编程
具体主题WeatherData的notifyObservers()方法中下面代码片段就是针对接口编程,
Observer observer = (Observer)observers.get(i); observer.update(temperature, humidity, pressure);
以及具体观察者中,下面代码片段:
weatherData.registerObserver(this);
public void update(float temperature, float humidity, float pressure) {...display(); }
原则4:为了交互对象之间的松耦合设计而努力
主题和观察者之间的依赖降到了最低,主题只要知道观察者实现了Observer接口,观察者只要向主题订阅/取消订阅即可。对于主题而言,观察者数量的增加或者减少,主题不需要任何改变。同样,主题内部实现方式的变化,对于观察者而言没有任何影响。
本章总结
主题(也就是可观察者)用一个共同的接口来更新观察者。
观察者和可观察者之间用松耦合方式结合,可观察者不需要知道观察者的细节,只需要知道它实现了观察者接口即可。
观察者可以从可观察者处push/pull数据,push的方式被认为是更好的。因为这样可观察者对象不要提供getXX() API,耦合性会更低。
push/pull数据时,主动方都是可观察者。观察者只有在被通知(update(Observable obs, Object arg)被调用)时,才去push/pull数据。在没有被通知时,不应该去pull数据,虽然这样也可以pull到数据。
有多个观察者时,不可以依赖特定的通知次序。
要注意java.util.Observable实现上带来的一些问题。
Java API包括多种观察者模式的实现,包括了通用的java.util.Observable。
观察者被用在许多地方,如JavaBeans,RMI,Swing。许多GUI框架都大量使用了该模式。
《HeadFirst设计模式》读书笔记-第2章-观察者模式相关推荐
- HeadFirst设计模式读书笔记--观察者模式(2)(二)
设计气象站(案例) 实现气象站 public interface Subject{/**这两个方法都需要观察者作为变量,该观察者是用来注册或被删除的*/public void registerObse ...
- Head First设计模式读书笔记八 第九章上 迭代器模式
之前的总结: https://blog.csdn.net/u011109881/article/details/59677544 个人觉得本章节,HeadFirst讲的没有之前看到的网站讲的清晰,至少 ...
- HeadFirst设计模式读书笔记
简单的做下笔记,以后找起来方便.设计原则通用,不针对哪个模式. 1 策略模式 定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户. 设计原则: 找出应用中可能需 ...
- Head First设计模式读书笔记八 第九章下 组合模式
之前的总结链接: https://blog.csdn.net/u011109881/article/details/58710579 对比headFirst书中的例子,我觉得书中的组合模式的例子比上面 ...
- 《HeadFirst设计模式》读书笔记-第9章v3-组合迭代器
定义 组合迭代器不是一个设计模式,是指如何在组合中使用迭代器.所以本章的代码是基于<HeadFirst设计模式>读书笔记-第9章v2-组合模式 修改过来的,需要先熟悉组合模式. 代码实现 ...
- 大话设计模式读书笔记
主题 概要 设计模式 大话设计模式读书笔记 编辑 时间 新建 20170423 序号 参考资料 1 大话设计模式 重新看了一遍设计模式,除了一些已经特别熟悉的模式,都自己敲了一遍代码,有些豁然开朗的感 ...
- 设计模式读书笔记-----工厂方法模式
一.问题 在前一章<设计模式读书笔记-----简单工厂模式>中通过披萨的实例介绍了简单工厂模式.在披萨实例中,如果我想根据地域的不同生产出不同口味的披萨,如纽约口味披萨,芝加哥口味披萨.如 ...
- 《Java编程思想》读书笔记 第十三章 字符串
<Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...
- Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据
Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 7.1 程序数据的命名 PL/SQL要求在给数据结构命名的时候应 ...
最新文章
- 报表 表格间距_从易读性和易操作性两大方面,教你做好表格设计
- 『转载』在vs2008(2005)winform中,打开office文档
- linux快速复制大量小文件方法
- 汇编 --- 初体验
- 浅谈前端路由原理hash和history
- vs2019配置OpenGL
- aws rds监控慢sql_AWS RDS SQL Server –启动新的数据库实例
- 设计模式学习01-策略模式
- 转:Visio 2010 产品秘钥 亲测可用的
- JOHNSON算法:流水作业最优调度问题
- 线性代数:03 向量空间 -- 向量空间的基与维数,坐标,过渡矩阵
- 基于SVM,KNN,CNN的数字图像识别
- Unity获取真实地理地图应用Terrain笔记
- vue.js解析lrc格式歌词文件
- 响应式背景图片的几种方法
- 联想G40进入BIOS
- jquerymobile-16 select menu
- 粉末成型工艺(粉末冶金粉末注射成型)
- 好用的vue瀑布流插件-vue-masonry
- 016 | 乡村振兴战略下农村宅基地有偿退出现状 | 大学生创新训练项目申请书 | 极致技术工厂
热门文章
- 第十二届蓝桥杯(2021年)模拟赛 Python组(第一期) 题目+个人解答
- 西游记中出现的女神仙
- 总有一条适合你:名人凡人经典语录200条
- 郭晶晶成功瘦身中环逛街 产后专心相夫教子
- python - 密码加密与解密
- **10种常用的网络营销方法**
- Google Earth Engine(GEE)——join连接在GEE中的应用(同一sentinel-2影像集合)含滑动窗口平滑影像过程
- android 8.1 蓝牙打不开之CLOCK_BOOTTIME_ALARM问题
- styled 手撸Switch开关
- 账号泄露,更换密码非常态?