# 观察者模式

# 现实案例

  • 现在有一个气象站,它能够获取到气象数据(温度,湿度,气压);
  • 一个 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 设计方案;
  • 观察者模式也被称为 “发布 - 订阅模式

# 观察者模式的实现

2020-2-15-19-57-50.png

  • 首先我们需要一个 “主题接口” 和一个 “观察者接口”;
  • 主题接口有:添加观察者(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);
    }
}

# 总结:观察者模式

  • 观察者模式提供了一种对象设计方式,让 “主题” 和 “观察者” 之间松耦合。也就是对象之间可以交互,但是互相并不依赖:
  • 主题只知道观察者实现了某个接口,主题不需要知道观察者的具体类是谁,做了什么或者其他任何细节
  • 因为主题对象唯一依赖的东西是一个实现了接口的对象列表,所以我们可以随时增加观察者。同样我们也可以在任何时候删除某些观察者
  • 观察者对象之间也可以是不同的类型,因为他们都实现了一个相同的接口;
  • 当有新的观察者类型出现时,主题类的代码不需要修改。因为主题对象只在乎观察者类型,是否实现了观察者接口;
  • 要我们遵守好观察者和主题对象的接口,我们就可以自由地更改他们。因为二者是松耦合的,两个类型之间并不会互相影响;
上次更新: 8/22/2020, 8:14:09 AM