观察者模式

1|0基本介绍

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)

意图:当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。

观察者模式属于行为型模式, 大多应用于一些事件驱动模型(Spring涉及)或者游戏开发领域。

 

假设有一家气象局,姑且就叫神盾气象局吧,该气象局委托我们构建一套系统,这个系统有两个公告牌,需要我们显示当前的实时天气和未来的天气预报。 当神盾气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据务必实时更新。神盾气象局同时要求我们保证程序拥有足够的可扩展性,因为以后随时要新增其他的公告牌(如紧急公告等)。

他们最初始的设计如下:

public class WeatherData { //实例变量声明 ... public void measurementsChanged() { float temperature = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); List<Float> forecastTemperatures = getForecastTemperatures(); //更新公告牌 currentConditionsDisplay.update(temperature, humidity, pressure); forecastDisplay.update(forecastTemperatures); } ... }

 

然鹅每当我需要删除或者新增公告牌时,我就必须得修改 核心逻辑代码,如此看来扩展性极差,违背了开闭原则(对扩展开放,对修改关闭)

从上述方案,我们已经大致了解该方案有很多问题

  • 扩展性较差
  • 代码功能耦合严重
  • 核心功能代码改来改去容易出问题

 

2|0观察者模式

观察者模式通常情况所解决的需求场景:A对象(观察者)被 B对象(被观察者)的某种变化高度敏感,需要在B变化的那一刻A及时得到反馈。

举个例子:小明过马路,小明需要在红绿灯由红灯变成绿灯后到达马路另一侧,这个场景中,小明是观察者,红绿灯是被观察者,当红绿灯发生颜色改变时,小明需要得到反馈。

但是程序中的观察和现实中所说的【观察】有些许差异:

  • 观察者不需要时刻盯着被观察者(小明不需要每一秒盯着红绿灯)
  • 采用注册或者订阅(Subscribe)的方式告诉被观察者(红绿灯变色后会广播,小明可以听到)

采取这样被动的观察方式,省去了反复检索状态的资源消耗,也能得到最快的反馈速度,其实就是由拉变成了推

 

观察者模式通常基于 Subject(主题)和 Observer(观察者)而设计,类图如下

 

既然我们说了神盾气象局最初的设计 多么的糟糕,那么我们现在就用观察者模式来重构它吧!

首先我们基于上述的通用类图,重新构建神盾气象局的结构类图以及编码实现

 

主题接口

/** * 主题 */ public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObserver(); }

 

观察者接口

/** * 观察者 */ public interface Observer { //反向更新 void update(WeatherDetail weatherDetail); }

 

公告牌接口

public interface DisplayElement { void display(); }

 

气象数据Entity

@Data @AllArgsConstructor public class WeatherDetail { private double temperature; //当前温度 private double humidity; //当前湿度 private double pressure; //当前气压 private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情 }

 

气象数据(被观察者)- 核心

@Data public class WeatherData implements Subject { private List<Observer> observers; private WeatherDetail weatherDetail; public WeatherData() { observers = new ArrayList<>(); } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { if (!observers.isEmpty()) { observers.remove(observer); } } @Override public void notifyObserver() { for (Observer observer : observers) { observer.update(weatherDetail); } } public void setMeasurements(WeatherDetail weatherDetail){ this.weatherDetail = weatherDetail; notifyObserver(); } }

 

显示当前天气的公告牌(CurrentConditionDisplay)

/** * 当前展板 */ public class CurrentConditionDisplay implements Observer, DisplayElement { private double temperature; //当前温度 private double humidity; //当前湿度 private double pressure; //当前气压 public CurrentConditionDisplay(Subject weatherData) { //天气数据 weatherData.registerObserver(this); } @Override public void display() { System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]"); } @Override public void update(WeatherDetail weatherDetail) { this.temperature = weatherDetail.getTemperature(); this.humidity = weatherDetail.getHumidity(); this.pressure = weatherDetail.getPressure(); } }

 

显示未来几天天气的公告牌(ForecastConditionDisplay)

ppublic class ForecastConditionDisplay implements Observer, DisplayElement { private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情 public ForecastConditionDisplay(Subject weatherData) { weatherData.registerObserver(this); } @Override public void display() { if (forecastDetails != null) { for (WeatherDetail weatherDetail : forecastDetails) { System.out.println("forecast-[temperature:" + weatherDetail.getTemperature() + ",humidity" + weatherDetail.getHumidity() + ",pressure" + weatherDetail.getPressure() + "]"); } } } @Override public void update(WeatherDetail weatherDetail) { forecastDetails = weatherDetail.getForecastDetails(); } }

 

到这里,我们整个神盾气象局的WeatherData应用就改造完成了。

两个公告牌 CurrentConditionsDisplayForecastConditionDisplay 实现了 ObserverDisplayElement 接口。在他们的构造方法中会调用 WeatherDataregisterObserver 方法将自己注册成观察者,这样被观察者 WeatherData 就会持有观察者的应用,并将它们保存到一个集合中。当被观察者 WeatherData 状态发生变化时就会遍历这个集合,循环调用观察者更新公告牌数据的方法。后面如果我们需要增加或者删除公告牌,就只需要新增或者删除实现了 ObserverDisplayElement 接口的公告牌就好了。

 

好,我们接下来测试一下利用观察者模式改进后的程序.....

public class Client { public static void main(String[] args) { List<WeatherDetail> forecastDetail = new ArrayList<>(); forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1, null)); forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3, null)); WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail); WeatherData weatherData = new WeatherData(); DisplayElement current = new CurrentConditionDisplay(weatherData); DisplayElement forecast = new ForecastConditionDisplay(weatherData); weatherData.setMeasurements(weatherDetail); current.display(); forecast.display(); } }

输出结果

 

3|0观察者模式(JDK版)

我们还是用神盾气象局的例子来实现JDK版本的观察者模式,这样便于比较两者之间的差别
 
公告牌接口

public interface DisplayElement { void display(); }

 
气象数据Entity

@Data @AllArgsConstructor public class WeatherDetail { private double temperature; //当前温度 private double humidity; //当前湿度 private double pressure; //当前气压 private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情 }

 
气象数据(被观察者)- 核心

/** * 天气数据 */ @Data public class WeatherData extends Observable { private WeatherDetail weatherDetail; public WeatherData() {} public void setMeasurements(WeatherDetail weatherDetail){ this.weatherDetail = weatherDetail; this.setChanged(); notifyObservers(weatherDetail); } }

 
显示当前天气的公告牌(CurrentConditionDisplay)

/** * 当前展板 */ public class CurrentConditionDisplay implements Observer, DisplayElement { private double temperature; //当前温度 private double humidity; //当前湿度 private double pressure; //当前气压 public CurrentConditionDisplay(Observable weatherData) { //天气数据 weatherData.addObserver(this); } @Override public void display() { System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]"); } @Override public void update(Observable observable, Object arg) { WeatherDetail weatherDetail = (WeatherDetail) arg; this.temperature = weatherDetail.getTemperature(); this.humidity = weatherDetail.getHumidity(); this.pressure = weatherDetail.getPressure(); } }

 
显示未来几天天气的公告牌(ForecastConditionDisplay)

public class ForecastConditionDisplay implements Observer, DisplayElement { private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情 public ForecastConditionDisplay(Observable weatherData) { //天气数据 weatherData.addObserver(this); } @Override public void display() { if (forecastDetails != null) { for (WeatherDetail weatherDetail : forecastDetails) { System.out.println("forecast-[temperature:" + weatherDetail.getTemperature() + ",humidity:" + weatherDetail.getHumidity() + ",pressure:" + weatherDetail.getPressure() + "]"); } } } @Override public void update(Observable observable, Object arg) { WeatherDetail weatherDetail = (WeatherDetail) arg; this.forecastDetails = weatherDetail.getForecastDetails(); } }

 
到这里,我们使用JDK自带的API实现了观察者模式,下面我们开始测试

public class Client { public static void main(String[] args) { List<WeatherDetail> forecastDetail = new ArrayList<>(); forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1,null)); forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3,null)); WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail); WeatherData weatherData = new WeatherData(); DisplayElement current = new CurrentConditionDisplay(weatherData); DisplayElement forecast = new ForecastConditionDisplay(weatherData); weatherData.setMeasurements(weatherDetail); current.display(); forecast.display(); } }

输出结果

 
总结:使用JDK自带的API去实现观察者模式固然方便,但是由于需要继承 Observable 接口,会对被观察者类造成限制(单继承的局限性),其次 Observable 的代码 从属JDK1.0,底层还是用的相关的Vector去做安全的集合容器,个人感觉还是有点过时了,个人还是倾向于自实现观察者模式。


__EOF__

本文作者丁可乐
本文链接https://www.cnblogs.com/dwlovelife/p/13352651.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   丁可乐  阅读(798)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示