观察者模式

定义

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

Java内置的观察者
  1. Java API有内置的观察者。java.util包(package)内包含最基本的Observer接口与Observable类。
  2. 现在我们实现一个气象站,当天气(温度、湿度、气压)有变化的时候,我们就更新目前状况、气象统计、天气预报的显示布告板。
  3. 气象站就是我们的主题(可观察者),那它就直接继承Observable。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
    }

    public void measurementsChanged() {
    setChanged();
    //该方法会通知所有订阅的观察者;未直接传送数据。 观察者想获取数据需要自行拉取。
    notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    measurementsChanged();
    }

    public float getTemperature() {
    return temperature;
    }

    public void setTemperature(float temperature) {
    this.temperature = temperature;
    }

    public float getHumidity() {
    return humidity;
    }

    public void setHumidity(float humidity) {
    this.humidity = humidity;
    }

    public float getPressure() {
    return pressure;
    }

    public void setPressure(float pressure) {
    this.pressure = pressure;
    }
    }
  4. Observable类已经实现了addObserver增加,deleteObserver删除,notifyObserversnotifyObservers(Object arg)通知观察者的方法。要成为观察者只需实现Observer接口,并注册到对应主题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //目前状况 布告板
    public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
    observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {

    if(o instanceof WeatherData){
    WeatherData weatherData = (WeatherData)o;
    this.temperature = weatherData.getTemperature();
    this.humidity = weatherData.getHumidity();
    display();
    }

    }

    @Override
    public void display() {
    System.out.println("Current conditions:" + temperature + "F degress and " + humidity + "% humidity");
    }

    }
  5. 上面的主题是通过notifyObservers() 无参方式通知观察者,所以观察者通过拉的方式获取了主题的数据,达到了拿你所需。当然我们也可以在主题中通过notifyObservers(Object arg) 方法,把数据通过推的形式传送给观察者,这样的话,可能会造成数据冗余。其他的StatisticsDisplay统计和ForecastDisplay预测布告板就不一一实现了。

  6. Observable的黑暗面:首先,Observable是一个类,你必须设计一个类继承它,如果某类想同时具有Observable类和另一超类的行为,就会陷入两难。再者,因为没有Observable接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用。而且setChanged()方法被保护起来,这意味着除非你继承Observable,否则无法创建Observable实例并组合到你自己的对象中来。对于Observable已经实现的方法无法再做修改,例如通知观察者的顺序。
定制面向接口实现观察者模式。

a. 定义主题接口Subject,包含注册,删除,通知三个基本方法。
b. 定义观察者接口Observer,一个update方法,当主题状态改变是它被调用
c. WeatherData,一个具体主题总是实现主题接口,除了实现主题已经定义的方法,具体主题也可以添加其他的属性、方法或实现其他接口。
d. 具体的观察者可以是实现Observer接口的任意类。观察者必须注册具体主题,以便接收更新。

  1. 主题接口

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Subject {
    //注册
    void registerObserver(ObserverMyself o);
    //删除
    void removeObserver(ObserverMyself o);
    //通知所有观察者
    void notifyObservers();
    }
  2. 观察者接口

    1
    2
    3
    4
    public interface ObserverMyself {
    //当气象观测值改变时,主题把这些状态值当作方法当参数,传送给观察者。
    void update(float temp,float humidity,float pressure);
    }
  3. 布告板接口

    1
    2
    3
    public interface DisplayElement {
    void display();
    }
  4. 气象站

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class WeatherData implements Subject {
    private ArrayList<ObserverMyself> observers;

    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
    observers = new ArrayList();
    }


    @Override
    public void registerObserver(ObserverMyself o) {
    observers.add(o);
    }

    @Override
    public void removeObserver(ObserverMyself o) {
    int i = observers.indexOf(o);
    if(i>=0){
    observers.remove(i);
    }
    }

    @Override
    public void notifyObservers() {
    for (ObserverMyself observer : observers) {
    observer.update(temperature,humidity,pressure);
    }
    }

    public void measurementsChanged(){
    notifyObservers();
    }

    // public void setMeasurements(float temperature,float humidity,float pressure){
    // this.temperature = temperature;
    // this.humidity = humidity;
    // this.pressure = pressure;
    // measurementsChanged();
    // }

    }
  5. 目前状况 布告板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class CurrentConditionsDisplay implements ObserverMyself, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
    }


    @Override
    public void update(float temp, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    display();
    }

    @Override
    public void display() {
    System.out.println("Current conditions:" + temperature + "F degress and " + humidity + "% humidity");
    }
    }

观察者保存对Subject的引用是为了防止以后可能想取消注册。该模式下实现的观察者模式使用了推的方式传送数据。

总结

对于具体应该是自己实现一个观察者还是使用jdk自带的,就需要看实际情况了。不管用哪一种方法,反正你都已经熟悉观察者模式了,应该都能善用它们。