二、观察者模式(Observer Pattern)《HeadFirst设计模式》读书笔记

  1. 我们现在要写一个气象监测的应用,它的需求如下:

    只要当天气发生变化的时候,就要更新三个布告板的显示,分别是目前天气状况(温度、湿度和气压)、气象统计(平均值等)和天气预报三个布告板。布告板要可扩展,未来可能会有新的布告板

  加入进来。

    有关天气的原始数据我们可以通过提供好的WeatherData对象获得,它提供了几个方法,getTemperature()、getHumidity()、getPressure()用于获得当前的温度、湿度和气压,还有一个 

  measurementsChanged()方法,一旦气象数据更新时,此方法就会自动被调用。 

                                   

  2. 我们可以通过在measurementsChanged()方法内写入布告板显示的代码,比如先加入一个目前天气状况的布告板显示更新,这样天气发生变化时,随着measurementsChanged方法被调用,我们的代

码就执行了,布告板也就更新了:

public void measurementsChanged(){ 
    float temp = getTemperature(); 
    float humidity= getHumidity(); 
    float pressure= getPressure(); 
  //目前天气状况的布告板显示更新 
  currentConditionssDisplay.update(temp,humidity,pressure); 
}

 

  但是这样代码耦合在一起了,如果以后需要加入新的布告板,只能修改当前代码。因此我们就可以采用观察者模式。

  3. 观察者模式:

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

  可以理解为微博的关注,一个微博大V可以被很多人关注,当他发动态时就会通知到所有的粉丝。这里的微博大V就是一个主题,而关注他的粉丝就是观察者,当主题变化的时候,观察者会收到通知并执

行下一步动作。当然观察者同时也可以是一个主题,反之亦然。

  在上面气象监测的应用中,我们也可以采用观察者模式,将WeatherData当成一个主题,将布告板当成观察者,让布告板订阅WeatherData这个主题,这样一旦天气发生变化,所有订阅了的布告板就都会

收到通知,就可以执行更新操作了。通过观察者模式我们可以实现将观察者和主题解耦,观察者可以随时订阅和取消订阅对应的主题,而不用影响主题。

   上面说了观察者模式的适用场景和好处,具体的实现可以参照下图:

  

  因此我们可以让WeatherData实现主题接口,让三个布告板实现观察者接口,并通过主题提供的注册方法让观察者都订阅WeatherData。

  首先新建一个主题接口:

public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}

  新建观察者接口:

public interface Observer {
    void update(float temp,float humidity,float pressure);
}

  新建一个显示接口用于布告板的显示:

public interface DisplayElement {
    //当布告板需要显示时,实现此方法
    void display();
}

  WeatherData实现Subject接口,放一个一个ArrayList类来存放观察者:

public class WeatherData implements Subject {
    //定义温度、适度和气压
    private float temp;
    private float humidity;
    private float pressure;

    //创建一个ArrayList用于存放订阅此主题的观察者,通过构造方法初始化
    private ArrayList<Observer> observers;

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

    //将观察者注册到主题,即添加到ArrayList中
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    //将观察者移除,即从ArrayList中remove
    @Override
    public void removeObserver(Observer observer) {
        //先判断下observer是否存在,不存在会返回-1
        int i = observers.indexOf(observer);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    //通知所有观察者,即调用所有观察和的update()方法,,将更新的温度、湿度、气压传进去
    @Override
    public void notifyObserver() {
        for (Observer observer : observers) {
            observer.update(temp, humidity, pressure);
        }
    }

    //天气变化时自动调用的方法
    public void measurementsChanged(){
        notifyObserver();
    }

    //模拟自动获得气象参数更新的方法,通过set方法更新参数,并调用measurementsChanged()方法
    public void setParameters(float temp,float humidity,float pressure){
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

  新建一个显示当前天气状况的布告板CurrentConditionsDisplay ,实现Observer接口,另外实现DisplayElement接口,重写display()方法显示布告板内容。

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    //定义温度、湿度和气压
    private float temp;
    private float humidity;
    //保存主题是为了让以后移除观察者时更加方便
    private WeatherData weatherData;

    //在构造方法中直接完成观察者对主题的注册
    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

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

    //显示当时的温度和气压
    @Override
    public void display() {
        System.out.println("Current Conditions:" + temp + "F degrees and" + humidity + "% humidity");
    }
}

  上面在构造方法中注册了主题,并保存了WeatherData对象,书中说是为了以后移除观察者的时候方便。

  下面测试一下:

public class Test02 {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        //新建布告板,构造方法中会将观察者注册到weatherData
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        //模拟天气状况发生改变
weatherData.setParameters(80,65,30.4f); weatherData.setParameters(78,63,29.4f); weatherData.setParameters(76,61,28.4f); } }

  输出结果:

   

   可以看到,每次天气发生变化,主题都会通知到观察者做出更新。当我们有新的观察者时,只需要实现Observer并注册到WeatherData,就会自动接到通知了。

  4. 其实Java内置了观察者模式,在上面的例子中,我们发现,观察者只能被动的接受通知,不能主动获取主题的状态。而在Java内置的Observer模式既支持被动接收,也支持主动拉取。

  Java内置的观察者模式为java.util包下的Observable类和Observer接口,Observable类就相当于一个主题,可被观察的,Observer就是观察者。不同之处在于Observable是一个类而不是接口,而且有

一个changed标志位,用来标志当前状态是否改变,如果为true,就通知管观察者,并提供了setChanged()来将changed置为true,可通过clearChanged()清除改变状态(将changed置为false,hasChanged()

返回changed的当前值。通过changed标志可以更灵活的控制通知,比如想在某种情况下再通知而不是已有改变就进行通知。

  此外Observable中同样提供了添加、删除和通知Observer的方法。Observer接口则只有一个update(Observable o, Object arg)方法,参数中Observable 用来表示是哪个主题,Object为传递的数据对象。

  除此之外,Observable类中的notifyObservers()方法是倒序通知观察者的,因此在使用观察者模式的时候不能太依赖于观察者被通知的次序,如果这样的话,一旦实现方式有所改变,通知次序就会改变,

耦合度就变高了。

  5. 总结:

    1)观察者模式适用于一对多的关系中,一个对象的状态发生改变,可以通知到多个依赖它的对象进行更新。

    2)java自带的观察者模式的主题即可观察者Observable是一个类而不是接口,限制了它的使用和复用,因为类是单继承而接口可以多继承。因此如果有需要的话我们可以自己写主题接口。

  

  

 

       

posted @ 2020-06-15 22:49  ADvancedCZ  阅读(137)  评论(0编辑  收藏  举报