观察者模式 :定义了一系列对象之间的一对多关系;当一个对象改变状态,其他依赖者都会收到通知并自动更新。
气象站监测应用的例子,此系统中的三个部分是:
- 气象站:获取实际气象数据的物理装置;(该对象与观察者模式关系不大)。
- WeatherData对象:追踪来气象站的数据,并更新布告板;属性有温度、湿度、气压。
- 布告板:显示目前天气状况给用户看。有不同的布告板:1、展示天气实时情况,2、气象统计,3、天气预报等等不同布告板。
直接编程实现方式
关系图
WeatherData代码
/**
* @author gu
* @date 2020/6/14 17:13
* 气象数据
*/
public class WeatherData {
// 其他代码
private CurrentConditionalDisplay currentConditionalDisplay;
private StatisticsDisplay statisticsDisplay;
private ForecastDisplay forecastDisplay;
/**
* 当气象测量更新时,该方法会被调用;
* 方法里面调用了每个布告板的update方法
*
*/
public void measurementsChanged(){
float temperature = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionalDisplay.update(temperature,humidity,pressure);
statisticsDisplay.update(temperature,humidity,pressure);
forecastDisplay.update(temperature,humidity,pressure);
}
// 其他代码
}
这种实现方式的缺点:
- 针对具体的实现编程,而不是针对接口;
- 对于新增一个布告板,就要修改WheatherData的代码实现;
- 无法动态的增加或删除布告板;
- 没有封装改变的部分;
- 破坏了weatherData类的封装。
改进
应该使用观察者模式实现该功能:
主题和观察者定义了一对多的关系;观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。
使用观察者实现
为了交互对象之间的松耦合设计而努力。观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
设计类图
主题只知道观察者实现了某个接口;主题不需要知道观察者的具体类是谁、作了些什么或者其他任何细节。任何时候我们都可以新增或删除观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以可以随时的改变观察者的个数。
代码实现
WeatherData类:
/**
* @author gu
* @date 2020/6/14 17:13
* 气象数据
*/
public class WeatherData implements Subject{
private final ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(ArrayList<Observer> observers) {
this.observers = observers;
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if(i >= 0){
observers.remove(i);
}
}
@Override
public void notifyObserver() {
observers.forEach(o -> {
o.update(getTemperature(),getHumidity(),getPressure());
});
}
public void measurementsChanged(){
notifyObserver();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = temperature;
this.pressure = pressure;
measurementsChanged();
}
// 其他代码
}
CurrentConditionalDisplay实现
/**
* @author gu
* @date 2020/6/14 17:18
* 展示当前气象的布告板
*/
public class CurrentConditionalDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
// 保留subject的引用,当想要主动注销时比较方便,在自身添加一个注销方法调用weatherData的注销方法即可
private final Subject weatherData;
public CurrentConditionalDisplay(Subject weatherData) {
this.weatherData = weatherData;
// 将自己注册到主题
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
display();
}
/**
* 只显示温度和湿度
*/
@Override
public void display() {
System.out.println("CurrentConditionalDisplay: temperature = [" + temperature + "], humidity = [" + humidity + "]");
}
}
ForecastDisplay
和StatisticsDisplay
两个类的实现类似,只是display()
方法不同:
ForecastDisplay:
@Override
public void display() {
System.out.println("ForecastDisplay: temperature = [" + temperature + "], humidity = [" + humidity + "], pressure = [" + pressure + "]");
}
StatisticsDisplay:
@Override
public void display() {
System.out.println("StatisticsDisplay: tody temperature = [" + temperature + "], humidity = [" + humidity + "], pressure = [" + pressure + "]");
}
以上主题将参数传给观察者使用的是主动将所有参数推送过去的;也可以在主题中添加相应的方法,update()
方法不设置参数,让每个观察者去拉数据,这样观察者可以按需索取,但是主题会被调用多次。
jdk自带的观察者模式
主题:Observable
观察者Observer
可以看出,jdk的Observable
类提供了两种数据交换的方法,主动传参和被动拉去;
在java的内置观察者模式中,主题Observable
是以类的形式出现的;多了几个change相关的方法;
主题推送消息的步骤:
- 先调用
setChange()
方法,标记状态已经改变的事实; - 然后调用两个
notifyObserver()
方法中的其中一个。
setChanged()方法用来标记状态十分方式了改变,好让notifyObservers()知道当它被调用时应该更新观察者;如果调用notifyObservers()之前没有先调用setChanged()方法,观察者就不会被通知。
这样做的必要性:setChanged()方法可以在更新观察者时有更多的弹性,选择更适当地时候通知观察者。比如:当温度变化超过一度时才通知观察者,而不是温度变化十分之一度就告知观察者。
同时还有clearChange()方法,和hasChanged()方法。
观察者接收通知:
// o 主题本身作为第一个参数传给观察者,告知观察者是哪个主题通知它
// arg 主题传给观察者的参数,为null说明为空
void update(Observable o, Object arg);
jdk的实现方式有些缺点:Observable不是一个接口,而是一个类。jdk中swing中大多数都是用了监听者模式来实现。