观察者模式
基本介绍
观察者模式(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);
}
...
}
然鹅每当我需要删除或者新增公告牌时,我就必须得修改 核心逻辑代码,如此看来扩展性极差,违背了开闭原则(对扩展开放,对修改关闭)
从上述方案,我们已经大致了解该方案有很多问题
- 扩展性较差
- 代码功能耦合严重
- 核心功能代码改来改去容易出问题
观察者模式
观察者模式通常情况所解决的需求场景: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应用就改造完成了。
两个公告牌 CurrentConditionsDisplay
和 ForecastConditionDisplay
实现了 Observer
和 DisplayElement
接口。在他们的构造方法中会调用 WeatherData
的 registerObserver
方法将自己注册成观察者,这样被观察者 WeatherData
就会持有观察者的应用,并将它们保存到一个集合中。当被观察者 WeatherData
状态发生变化时就会遍历这个集合,循环调用观察者更新公告牌数据的方法。后面如果我们需要增加或者删除公告牌,就只需要新增或者删除实现了 Observer
和 DisplayElement
接口的公告牌就好了。
好,我们接下来测试一下利用观察者模式改进后的程序…..
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版)
我们还是用神盾气象局的例子来实现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去做安全的集合容器,个人感觉还是有点过时了,个人还是倾向于自实现观察者模式。