笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

观察者模式

1、天气预报需求

具体要求如下:

  • 1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
  • 2)需要设计开放型 API,便于其他第三方也能接入气象站获取数据
  • 3)提供温度、气压和湿度的接口
  • 4)测量数据更新时,要能实时的通知给第三方

image.png

2、天气预报需求方案之普通方案

WeatherData类

通过对气象站项目的分析,我们可以初步设计出一个WeatherData

image.png

  • 1)通过getXxx方法,可以让第三方接入,并得到相关信息
  • 2)当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

image.png

CurrentConditions(当前的天气情况)可以理解成是我们气象局的网站

核心代码

气象网站类

  1. /**
  2. * 当前的天气情况:可以理解成是气象局的网站
  3. */
  4. public class CurrentConditions {
  5. private Float temperature;
  6. private Float pressure;
  7. private Float humidity;
  8. /**
  9. * 更新天气情况,通过推送的方式,由 WeatherData 调用
  10. *
  11. * @param temperature
  12. * @param pressure
  13. * @param humidity
  14. */
  15. public void update(Float temperature, Float pressure, Float humidity) {
  16. // 更新最新天气数据
  17. this.temperature = temperature;
  18. this.pressure = pressure;
  19. this.humidity = humidity;
  20. // 展示最新天气数据
  21. display();
  22. }
  23. /**
  24. * 公告板展示天气情况
  25. */
  26. public void display() {
  27. System.out.println("============最新天气============");
  28. System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
  29. System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
  30. System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
  31. }
  32. }

气象数据类

  1. /**
  2. * 核心类
  3. * 1、包含最新的天气信息情况
  4. * 2、含有 CurrentConditions 对象
  5. * 3、当数据更新时,主动调用 CurrentConditions 的 update() 方法
  6. */
  7. public class WeatherData {
  8. private Float temperature;
  9. private Float pressure;
  10. private Float humidity;
  11. private CurrentConditions conditions;
  12. /**
  13. * 传入 CurrentConditions 对象
  14. *
  15. * @param conditions
  16. */
  17. public WeatherData(CurrentConditions conditions) {
  18. this.conditions = conditions;
  19. }
  20. public Float getTemperature() {
  21. return temperature;
  22. }
  23. public Float getPressure() {
  24. return pressure;
  25. }
  26. public Float getHumidity() {
  27. return humidity;
  28. }
  29. /**
  30. * 推送天气数据到网站
  31. */
  32. public void dataChange() {
  33. conditions.update(getTemperature(), getPressure(), getHumidity());
  34. }
  35. /**
  36. * 当天气数据发生变化时进行更新
  37. *
  38. * @param temperature
  39. * @param pressure
  40. * @param humidity
  41. */
  42. public void setData(Float temperature, Float pressure, Float humidity) {
  43. this.temperature = temperature;
  44. this.pressure = pressure;
  45. this.humidity = humidity;
  46. dataChange();
  47. }
  48. }

客户端调用

  1. // 创建气象网站对象
  2. CurrentConditions currentConditions = new CurrentConditions();
  3. // 创建气象数据对象,并传入气象网站对象
  4. WeatherData weatherData = new WeatherData(currentConditions);
  5. // 天气发生变化时,更新最新的气象数据
  6. weatherData.setData(10f, 150f, 40f);
  7. //weatherData.setData(15f, 130f, 60f);
  8. //weatherData.setData(13f, 160f, 20f);

测试结果

  1. ============最新天气============
  2. *** 当前温度:10.0 ***
  3. *** 当前气压:150.0 kPa ***
  4. *** 当前湿度:40.0 %RH ***
  5. ============最新天气============
  6. *** 当前温度:15.0 ***
  7. *** 当前气压:130.0 kPa ***
  8. *** 当前湿度:60.0 %RH ***
  9. ============最新天气============
  10. *** 当前温度:13.0 ***
  11. *** 当前气压:160.0 kPa ***
  12. *** 当前湿度:20.0 %RH ***

问题分析

  • 1)其他第三方接入气象站获取数据的问题
  • 2)无法在运行时动态的添加第三方(新浪网站)
  • 3)违反OCP原则 => 观察者模式

WeatherData中增加第三方时,都需要创建对应的第三方公台板对象并加入到dataChange()方法中,既不是动态加入,也不利于维护

3、观察者模式原理

观察者模式类似订牛奶业务

  • 1)奶站 / 气象局:Subject
  • 2)用户 / 第三方网站:Observer

Subject:登记注册、移除和通知

image.png

  • 1)registerObserver():注册
  • 2)removeObserver():移除
  • 3)notifyObservers():通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定

Observer:接收输入

image.png

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为ObserverSubject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户是Observer,是多的一方

4、天气预报需求方案之观察者模式

UML 类图

image.png

image.png

核心代码

观察者对象**Observer**

  1. /**
  2. * 观察者接口
  3. */
  4. public interface Observer {
  5. void update(Float temperature, Float pressure, Float humidity);
  6. }
  7. /**
  8. * 观察者实现
  9. */
  10. public class CurrentConditions implements Observer {
  11. private Float temperature;
  12. private Float pressure;
  13. private Float humidity;
  14. @Override
  15. public void update(Float temperature, Float pressure, Float humidity) {
  16. // 更新最新天气数据
  17. this.temperature = temperature;
  18. this.pressure = pressure;
  19. this.humidity = humidity;
  20. // 展示最新天气数据
  21. display();
  22. }
  23. /**
  24. * 公告板展示天气情况
  25. */
  26. public void display() {
  27. System.out.println("============最新天气============");
  28. System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
  29. System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
  30. System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
  31. }
  32. }

主体对象**Subject**

  1. /**
  2. * 主体对象接口
  3. */
  4. public interface Subject {
  5. void registerObserver(Observer o);
  6. void removeObserver(Observer o);
  7. void notifyObservers();
  8. }
  9. /**
  10. * 主体对象实现
  11. */
  12. public class WeatherData implements Subject {
  13. private Float temperature;
  14. private Float pressure;
  15. private Float humidity;
  16. private List<Observer> observerList;
  17. public WeatherData() {
  18. observerList = new ArrayList<>();
  19. }
  20. public Float getTemperature() {
  21. return temperature;
  22. }
  23. public Float getPressure() {
  24. return pressure;
  25. }
  26. public Float getHumidity() {
  27. return humidity;
  28. }
  29. /**
  30. * 推送天气数据到网站
  31. */
  32. public void dataChange() {
  33. notifyObservers();
  34. }
  35. /**
  36. * 当天气数据发生变化时进行更新
  37. *
  38. * @param temperature
  39. * @param pressure
  40. * @param humidity
  41. */
  42. public void setData(Float temperature, Float pressure, Float humidity) {
  43. this.temperature = temperature;
  44. this.pressure = pressure;
  45. this.humidity = humidity;
  46. dataChange();
  47. }
  48. @Override
  49. public void registerObserver(Observer o) {
  50. observerList.add(o);
  51. }
  52. @Override
  53. public void removeObserver(Observer o) {
  54. if(o!= null && observerList.contains(o)) {
  55. observerList.remove(o);
  56. }
  57. }
  58. @Override
  59. public void notifyObservers() {
  60. for (Observer observer : observerList) {
  61. observer.update(temperature, pressure, humidity);
  62. }
  63. }
  64. }

观察者对象**Observer**

  1. public interface Observer {
  2. void update(Float temperature, Float pressure, Float humidity);
  3. }

调用测试

  1. // 创建气象网站对象
  2. CurrentConditions currentConditions = new CurrentConditions();
  3. // 创建气象数据对象
  4. WeatherData weatherData = new WeatherData();
  5. // 注册气象网站对象
  6. weatherData.registerObserver(currentConditions);
  7. // 天气发生变化时,更新最新的气象数据
  8. weatherData.setData(10f, 150f, 40f);
  9. //============最新天气============
  10. //*** 当前温度:10.0 ℃ ***
  11. //*** 当前气压:150.0 kPa ***
  12. //*** 当前湿度:40.0 %RH ***

观察者模式的好处

  • 1)观察者模式设计后,会以集合的方式来管理用户Observer,包括注册、移除和通知
  • 2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则

例如,我们新增SinaWebSiteBaiDuWebSite两个三方网站,接口气象局。此时三方只需实现相应接口即可,WeatherData不需要有任何的改变

  1. /**
  2. * 新增的三方观察者对象——新浪网
  3. */
  4. public class SinaWebSite implements Observer {
  5. private Float temperature;
  6. private Float pressure;
  7. private Float humidity;
  8. /**
  9. * 更新天气情况,通过推送的方式,由 WeatherData 调用
  10. *
  11. * @param temperature
  12. * @param pressure
  13. * @param humidity
  14. */
  15. @Override
  16. public void update(Float temperature, Float pressure, Float humidity) {
  17. // 更新最新天气数据
  18. this.temperature = temperature;
  19. this.pressure = pressure;
  20. this.humidity = humidity;
  21. // 展示最新天气数据
  22. display();
  23. }
  24. /**
  25. * 公告板展示天气情况
  26. */
  27. public void display() {
  28. System.out.println("============新浪网-最新天气============");
  29. System.out.println("*** 新浪网-当前温度:" + this.temperature + " ℃ ***");
  30. System.out.println("*** 新浪网-当前气压:" + this.pressure + " kPa ***");
  31. System.out.println("*** 新浪网-当前湿度:" + this.humidity + " %RH ***");
  32. }
  33. }
  34. /**
  35. * 新增的三方观察者对象——百度网
  36. */
  37. public class BaiDuWebSite implements Observer {
  38. private Float temperature;
  39. private Float pressure;
  40. private Float humidity;
  41. /**
  42. * 更新天气情况,通过推送的方式,由 WeatherData 调用
  43. *
  44. * @param temperature
  45. * @param pressure
  46. * @param humidity
  47. */
  48. @Override
  49. public void update(Float temperature, Float pressure, Float humidity) {
  50. // 更新最新天气数据
  51. this.temperature = temperature;
  52. this.pressure = pressure;
  53. this.humidity = humidity;
  54. // 展示最新天气数据
  55. display();
  56. }
  57. /**
  58. * 公告板展示天气情况
  59. */
  60. public void display() {
  61. System.out.println("============百度网-最新天气============");
  62. System.out.println("*** 百度网-当前温度:" + this.temperature + " ℃ ***");
  63. System.out.println("*** 百度网-当前气压:" + this.pressure + " kPa ***");
  64. System.out.println("*** 百度网-当前湿度:" + this.humidity + " %RH ***");
  65. }
  66. }

调用测试

  1. // 新增三方气象网站,只需注册即可
  2. weatherData.registerObserver(new SinaWebSite());
  3. weatherData.registerObserver(new BaiDuWebSite());
  4. // 天气发生变化时,更新最新的气象数据
  5. weatherData.setData(15f, 120f, 80f);
  6. //============最新天气============
  7. //*** 当前温度:15.0 ℃ ***
  8. //*** 当前气压:120.0 kPa ***
  9. //*** 当前湿度:80.0 %RH ***
  10. //============新浪网-最新天气============
  11. //*** 新浪网-当前温度:15.0 ℃ ***
  12. //*** 新浪网-当前气压:120.0 kPa ***
  13. //*** 新浪网-当前湿度:80.0 %RH ***
  14. //============百度网-最新天气============
  15. //*** 百度网-当前温度:15.0 ℃ ***
  16. //*** 百度网-当前气压:120.0 kPa ***
  17. //*** 百度网-当前湿度:80.0 %RH ***

当三方网站不再需要时,只要做相应的移除即可

  1. // 移除气象网站
  2. weatherData.removeObserver(currentConditions);
  3. weatherData.setData(20f, 160f, 30f);
  4. //============新浪网-最新天气============
  5. //*** 新浪网-当前温度:20.0 ℃ ***
  6. //*** 新浪网-当前气压:160.0 kPa ***
  7. //*** 新浪网-当前湿度:30.0 %RH ***
  8. //============百度网-最新天气============
  9. //*** 百度网-当前温度:20.0 ℃ ***
  10. //*** 百度网-当前气压:160.0 kPa ***
  11. //*** 百度网-当前湿度:30.0 %RH ***

5、JDK 源码分析

JDK 中的Observable就使用到了观察者模式

  1. public class Observable {
  2. private boolean changed = false;
  3. private Vector<Observer> obs = new Vector();
  4. public Observable() {
  5. }
  6. public synchronized void addObserver(Observer var1) {
  7. if (var1 == null) {
  8. throw new NullPointerException();
  9. } else {
  10. if (!this.obs.contains(var1)) {
  11. this.obs.addElement(var1);
  12. }
  13. }
  14. }
  15. public synchronized void deleteObserver(Observer var1) {
  16. this.obs.removeElement(var1);
  17. }
  18. public void notifyObservers() {
  19. this.notifyObservers((Object)null);
  20. }
  21. //...
  22. }
  23. public interface Observer {
  24. void update(Observable o, Object arg);
  25. }

角色分析和职责说明

  • 1)Observable即充当了Subject接口及其实现类,其中包括了Observer的集合,并且包括对观察者相关的注册、移除和通知等方法:addObserver()deleteObserver()notifyObservers(),这些方法就类似于我们上面例子中的registerObserver()removeObserver()notifyObservers()
  • 2)Observer即观察者接口,具有update()方法

image.png