设计模式(十九)观察者模式 Observer
- 模拟场景:
甲方提供了一个气象站的接口,气象站上面装有:温度感应装置、湿度感应装置、气压感应装置。
现在我们是乙方,需要设计一个 WeatherData 对象,从气象站获取数据,并且利用这些数据,更新三个布告板(当前状况、气象统计、天气预报)。
- 第一版解决方案:
通过简单地分析,我们可以很快确定一套解决方案:
WeatherData 提供一个 measurementsChanged() 方法,当这个方法被调用了,去实时获取气象站的数据,然后更新到三个布告板上。
public class BadWeatherData { @Getter private float temperature; @Getter private float humidity; @Getter private float pressure; private CurrentConditionsDisplay currentConditionDisplay; private StatisticsDisplay statisticsDisplay; private ForecastDisplay forecastDisplay; public BadWeatherData() { // some initialized function for displays } // We don't care how it be called, we only know is when it is called, we will update displays. public void measurementsChanged() { // We don't care how it gets data float temperature = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); currentConditionDisplay.update(temperature, humidity, pressure); statisticsDisplay.update(temperature, humidity, pressure); forecastDisplay.update(temperature, humidity, pressure); } }
- 第一套方案有什么问题?
显然,这是一个扩展性很差的解决方案,它有如下问题:
- 没有针对接口编程。(Display 应该事先一个公共的接口)
- 如果需要增加或者删除 Display,都要修改代码。
- 不能动态地增加或者删除 Display。
- 观察者模式:
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式 = 主题+观察者(Subject + Observer)。
- 理想的 WeatherData 设计方案:
- Display 对象,作为观察者,需要实现统一的 Observer 接口。
- Observer 接口,具有更新 temperature, humidity, pressure 的能力。
- WeatherData 对象,作为主题,需要实现 Subject 接口。
- Subject 接口,维护一个 Oberver 列表,对外提供将 Observer 加入/移除 列表的接口,并且具有通知所有 Observer 数据变化的能力。
- Observer 可以主动 订阅/取消 Subject。
- UML 类图(为了防止混乱,类图只显示一个 Display 对象):
- 第二版解决方案:
public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); }
public interface Observer { void update(float temperature, float humidity, float pressure); }
public interface DisplayElement { void display(); }
@Data public final class WeatherData implements Subject { private float temperature; private float humidity; private float pressure; private List<Observer> observers = new ArrayList<>(); public void measurementsChanged() { notifyObservers(); } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { observers.forEach(observer -> observer.update(temperature, humidity, pressure)); } }
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("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } }
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("Will show forecast related data"); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); } }
public class StatisticsDisplay implements Observer, DisplayElement { private float temperature; private Subject weatherData; public StatisticsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void display() { System.out.println("Statistics will show average temperature"); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; display(); } }