# 观察者模式
# 现实案例
- 现在有一个气象站,它能够获取到气象数据(温度,湿度,气压);
- 一个 WeatherData 对象,它能够获取到气象站的数据;
- 三个气象布告栏,分别显示当前气象数据,气象统计数据,和天气预报;
- WeatherData 对象获取完气象数据后,会通过某种方式更新三个布告栏的显示;
- 同时此系统可扩展,用户可以自定义新的布告栏,并加入其中。也可以删除已有布告栏;
# 方案一:❌ 针对具体实现编程:
- WeatherData 对象有
getTemperature
getHumidity
getPressure
三个方法,分别可以获取到温度,湿度,和气压; - 当有新的气象数据时,
measurementsChanged
方法会被调用(我们不管是怎么调用的); - 然后分别用上面的三个 getter 方法获取气象数据;
- 再将数据分别传入到布告栏实例的
update
方法。此方法会更新布告栏的显示;
// 针对具体实现
public class WeatherData {
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 一个一个地更新布告栏的显示
currentConditionDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}
# 方案一的问题:
- 上面的代码是针对具体实现编程,而不是针对接口编程;
- 每当有新的布告栏加入时,我们就要修改 WeatherData 的
measurementsChanged
方法; - 同时并没有提供一个,在运行时动态 “添加/删除” 布告栏的方法;
# 什么是观察者模式
- 观察者模式定义了对象之间的一对多依赖,一个 “主题对象” 对多个 “依赖者对象”。当主题对象改变状态时,它的所有依赖者都会收到通知并且触发对应操作;
- 在观察者模式中,主题是具有状态的对象。而观察者会使用这些状态,但是这些状态不属于它们;
- 观察者模式给这种依赖关系,提供了一个干净好用的 OO 设计方案;
- 观察者模式也被称为 “发布 - 订阅模式”
# 观察者模式的实现
- 首先我们需要一个 “主题接口” 和一个 “观察者接口”;
- 主题接口有:添加观察者(register),删除观察者(remove),通知观察者(notify)的方法;
- 观察者接口有一个 update 方法,当主题对象内的状态变化时,它被调用;
- 之后我们分别去实现这两个接口;
- 在实现主题类时,我们还需要一个类型为 “观察者接口集合” 的实例属性,以此来在 notify 方法中,引用该主题的所有观察者;
- 具体的观察者可以是实现了观察者接口的任意类;
- 观察者必须注册到一个具体的主题对象,以便接收更新;
- 主题对象通过一个观察者集合,去遍历地通知观察者新的状态。观察者之间并没有一个特定的通知次序,顺序是不确定的;
# 方案二:✅ 使用观察者模式
- 在这个案例中,WeatherData 是主题对象,布告板是观察者;
- 先写出接口:
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
// 这个是布告板的接口,所有的布告板都有 display 方法
public interface DisplayElement {
public void display();
}
- 然后再分别实现主题类,和观察者类:
// WeatherData 对象
public class WeatherData implements Subject {
private ArrayList<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
@Override
public void registerObserver(Observer o) {
this.observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = this.observers.indexOf(o);
if (i >= 0) {
this.observers.remove(o);
}
}
@Override
public void notifyObserver() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
//当从气象站得到更新观测值时,我们通知观察者
public void measurementsChanged() {
notifyObserver();
}
// 气象站通过这个方法,来把新的数据传进来
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// 预测气象数据布告板
public class ForecastDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
public ForecastDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println(temperature + " forecast " + humidity + " " + pressure);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
// 统计气象数据布告板
public class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public StatisticsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println(temperature + " statistics " + humidity);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
// 当前气象数据布告板
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);
}
@Override
public void display() {
System.out.println(temperature + " current " + humidity);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
- 最好让我们实现气象站的类:
public class WeatherStation {
public static void main(String[] args) {
// 创建一个 weatherData 对象
WeatherData weatherData = new WeatherData();
// 创建布告板,并且注册到 weatherData 对象
CurrentConditionsDisplay ccd = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay sd = new StatisticsDisplay(weatherData);
ForecastDisplay fd = new ForecastDisplay(weatherData);
// 把气象数据传给 weatherData 对象
weatherData.setMeasurements(1, 1, 1);
weatherData.setMeasurements(2, 3, 2);
}
}
# 总结:观察者模式
- 观察者模式提供了一种对象设计方式,让 “主题” 和 “观察者” 之间松耦合。也就是对象之间可以交互,但是互相并不依赖:
- 主题只知道观察者实现了某个接口,主题不需要知道观察者的具体类是谁,做了什么或者其他任何细节
- 因为主题对象唯一依赖的东西是一个实现了接口的对象列表,所以我们可以随时增加观察者。同样我们也可以在任何时候删除某些观察者;
- 观察者对象之间也可以是不同的类型,因为他们都实现了一个相同的接口;
- 当有新的观察者类型出现时,主题类的代码不需要修改。因为主题对象只在乎观察者类型,是否实现了观察者接口;
- 要我们遵守好观察者和主题对象的接口,我们就可以自由地更改他们。因为二者是松耦合的,两个类型之间并不会互相影响;