定义

观察者模式(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);}
}

两种观察者模式的对比:

  1. Observable是类,Subject是接口

    Observable是类的优点是子类的代码会更加简洁,子类中无需实现addObserver()/deleteObserver()/notifyObservers()等方法,因为父类已经实现了。

    缺点是因为Observable是类,所以要设计一个类来继承它。如果某类想同时具有Observable和另一个超类的行为,就会陷入两难,毕竟java不支持多重继承。这限制了Observable的复用潜力。

    另外,Observable中的setChanged()方法被设置为protected,这意味着,除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来(因为组合后的Observable实例无法访问setChanged()方法)。这个违反了OO原则3(多用组合,少用继承)。

  2. Observable有增加setChanged()方法,这样可以让你在更新观察者时,更具有弹性。比如在这个案例中,你可以选择在温度变化超过多少度时,才通知观察者,这样可以避免因为非常微小的温度变化而导致的频繁通知观察者。

  3. Observable提供了notifyObservers()和notifyObservers(Object arg)两个方法通知观察者。

    如果你想push数据给观察者,你可以把数据对象传递给notifyObservers(Object arg)方法,观察者实现的update(Observable obs, Object arg)方法被调用时,数据对象会通过arg参数传递给update方法。

    当调用notifyObservers()方法时,arg参数为空,这时需要观察者通过obs对象pull数据,本案例weatherData.getPressure就是pull数据的例子。

该模式体现了哪些OO原则

  1. 原则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();
    }
  2. 原则4:为了交互对象之间的松耦合设计而努力

    主题和观察者之间的依赖降到了最低,主题只要知道观察者实现了Observer接口,观察者只要向主题订阅/取消订阅即可。对于主题而言,观察者数量的增加或者减少,主题不需要任何改变。同样,主题内部实现方式的变化,对于观察者而言没有任何影响。

本章总结

  1. 主题(也就是可观察者)用一个共同的接口来更新观察者。

  2. 观察者和可观察者之间用松耦合方式结合,可观察者不需要知道观察者的细节,只需要知道它实现了观察者接口即可。

  3. 观察者可以从可观察者处push/pull数据,push的方式被认为是更好的。因为这样可观察者对象不要提供getXX() API,耦合性会更低。

  4. push/pull数据时,主动方都是可观察者。观察者只有在被通知(update(Observable obs, Object arg)被调用)时,才去push/pull数据。在没有被通知时,不应该去pull数据,虽然这样也可以pull到数据。

  5. 有多个观察者时,不可以依赖特定的通知次序。

  6. 要注意java.util.Observable实现上带来的一些问题。

  7. Java API包括多种观察者模式的实现,包括了通用的java.util.Observable。

  8. 观察者被用在许多地方,如JavaBeans,RMI,Swing。许多GUI框架都大量使用了该模式。

《HeadFirst设计模式》读书笔记-第2章-观察者模式相关推荐

  1. HeadFirst设计模式读书笔记--观察者模式(2)(二)

    设计气象站(案例) 实现气象站 public interface Subject{/**这两个方法都需要观察者作为变量,该观察者是用来注册或被删除的*/public void registerObse ...

  2. Head First设计模式读书笔记八 第九章上 迭代器模式

    之前的总结: https://blog.csdn.net/u011109881/article/details/59677544 个人觉得本章节,HeadFirst讲的没有之前看到的网站讲的清晰,至少 ...

  3. HeadFirst设计模式读书笔记

    简单的做下笔记,以后找起来方便.设计原则通用,不针对哪个模式. 1 策略模式 定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户. 设计原则: 找出应用中可能需 ...

  4. Head First设计模式读书笔记八 第九章下 组合模式

    之前的总结链接: https://blog.csdn.net/u011109881/article/details/58710579 对比headFirst书中的例子,我觉得书中的组合模式的例子比上面 ...

  5. 《HeadFirst设计模式》读书笔记-第9章v3-组合迭代器

    定义 组合迭代器不是一个设计模式,是指如何在组合中使用迭代器.所以本章的代码是基于<HeadFirst设计模式>读书笔记-第9章v2-组合模式 修改过来的,需要先熟悉组合模式. 代码实现 ...

  6. 大话设计模式读书笔记

    主题 概要 设计模式 大话设计模式读书笔记 编辑 时间 新建 20170423 序号 参考资料 1 大话设计模式 重新看了一遍设计模式,除了一些已经特别熟悉的模式,都自己敲了一遍代码,有些豁然开朗的感 ...

  7. 设计模式读书笔记-----工厂方法模式

    一.问题 在前一章<设计模式读书笔记-----简单工厂模式>中通过披萨的实例介绍了简单工厂模式.在披萨实例中,如果我想根据地域的不同生产出不同口味的披萨,如纽约口味披萨,芝加哥口味披萨.如 ...

  8. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

  9. Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据

    Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 7.1 程序数据的命名 PL/SQL要求在给数据结构命名的时候应 ...

最新文章

  1. 报表 表格间距_从易读性和易操作性两大方面,教你做好表格设计
  2. 『转载』在vs2008(2005)winform中,打开office文档
  3. linux快速复制大量小文件方法
  4. 汇编 --- 初体验
  5. 浅谈前端路由原理hash和history
  6. vs2019配置OpenGL
  7. aws rds监控慢sql_AWS RDS SQL Server –启动新的数据库实例
  8. 设计模式学习01-策略模式
  9. 转:Visio 2010 产品秘钥 亲测可用的
  10. JOHNSON算法:流水作业最优调度问题
  11. 线性代数:03 向量空间 -- 向量空间的基与维数,坐标,过渡矩阵
  12. 基于SVM,KNN,CNN的数字图像识别
  13. Unity获取真实地理地图应用Terrain笔记
  14. vue.js解析lrc格式歌词文件
  15. 响应式背景图片的几种方法
  16. 联想G40进入BIOS
  17. jquerymobile-16 select menu
  18. 粉末成型工艺(粉末冶金粉末注射成型)
  19. 好用的vue瀑布流插件-vue-masonry
  20. 016 | 乡村振兴战略下农村宅基地有偿退出现状 | 大学生创新训练项目申请书 | 极致技术工厂

热门文章

  1. 第十二届蓝桥杯(2021年)模拟赛 Python组(第一期) 题目+个人解答
  2. 西游记中出现的女神仙
  3. 总有一条适合你:名人凡人经典语录200条
  4. 郭晶晶成功瘦身中环逛街 产后专心相夫教子
  5. python - 密码加密与解密
  6. **10种常用的网络营销方法**
  7. Google Earth Engine(GEE)——join连接在GEE中的应用(同一sentinel-2影像集合)含滑动窗口平滑影像过程
  8. android 8.1 蓝牙打不开之CLOCK_BOOTTIME_ALARM问题
  9. styled 手撸Switch开关
  10. 账号泄露,更换密码非常态?