设计模式(十九)观察者模式 Observer

  • 模拟场景:

  甲方提供了一个气象站的接口,气象站上面装有:温度感应装置、湿度感应装置、气压感应装置。编程

  如今咱们是乙方,须要设计一个 WeatherData 对象,从气象站获取数据,而且利用这些数据,更新三个布告板(当前情况、气象统计、天气预报)。ide

 

  • 初版解决方案:

  经过简单地分析,咱们能够很快肯定一套解决方案:this

  WeatherData 提供一个 measurementsChanged() 方法,当这个方法被调用了,去实时获取气象站的数据,而后更新到三个布告板上。spa

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);
    }
}

 

  • 第一套方案有什么问题?

  显然,这是一个扩展性不好的解决方案,它有以下问题:设计

  1. 没有针对接口编程。(Display 应该事先一个公共的接口)
  2. 若是须要增长或者删除 Display,都要修改代码。
  3. 不能动态地增长或者删除 Display。

 

  • 观察者模式:

  定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的全部依赖者都会收到通知并自动更新。code

  观察者模式 = 主题+观察者(Subject + Observer)。server

 

  • 理想的 WeatherData 设计方案:
  1. Display 对象,做为观察者,须要实现统一的 Observer 接口。
  2. Observer 接口,具备更新 temperature, humidity, pressure 的能力。
  3. WeatherData 对象,做为主题,须要实现 Subject 接口。
  4. Subject 接口,维护一个 Oberver 列表,对外提供将 Observer 加入/移除 列表的接口,而且具备通知全部 Observer 数据变化的能力。
  5. 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();
    }
}