Loading

设计模式一:观察者模式

1 认识观察者模式

首先认识以下什么是观察者模式,举一个现实生活中的例子,出版社于订阅者。

  • 出版社负责出版报纸
  • 订阅者向出版社订阅报纸,出版社印刷好报纸后将报纸送给订阅者
  • 任何人都可以到出版社订阅报纸
  • 订阅报纸的人可以随时取消订阅

这就是观察者模式的基本逻辑了。在观察者模式中,常常将出版社的角色命名为主题(Subject),将订阅者命名为观察者(Observer),观察者模式也被称为发布-订阅模式

2 观察者模式类图

在这里插入图片描述
这是一个简单的观察者模式类图,首先,具体主题中需要有一个List集合用来保存注册的Observer,并提供addObserver和removeObserver方法,同时,还需要一个notify方法来通知所有的观察者,通知观察者的方式是调用观察者中的update方法。而所有的观察者都需要实现Observer接口。

3 举例

举一个《head first design patterns》中气象站的例子:
有一个气象站需要三种布告板,分别显示目前的状况、气象统计及简单的预报,它提供了一个WeatherData对象,可以获得温度、湿度、气压三个数据。布告板需要可扩展,既布告板可以随时增加。
这是一个典型的观察者模式,主题是WeatherData对象,观察者为布告板。当WheatherData对象监测到数据发生变化时,即通知所有的观察者,观察者可以随意增减。
主题对象如下:

import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject {
    private List<Observer> observerList;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData(){
        observerList = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observerList.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observerList.remove(o);
    }

    @Override
    public void notifyObservers() {
        observerList.forEach(observer -> observer.update(temperature,humidity,pressure));
    }

}

目前状况布告板:

// 目前状况布告板
public class CurrentConditionDisplay implements Observer {
    private float temperature;
    private float humidity;
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    public void display(){
        System.out.println("current condition: " + temperature + "F degree and " + humidity + "% humidity");
    }
}

可以看到,所有的Observer都实现一个udpate方法,用于获取更新的数据。
这样做有一些局限性,比如,update方法接受的参数是固定的,如果以后想增加数据,比如酷热指数,就需要修改upate接口,这样所有以前的布告板都需要修改,这显然是一个很大的缺点,因为这种情况发生的概率往往很高,记住,软件开发的一个不变真理就是变化。那有什么办法呢,书中提供了一种方法,个人认为还有一种方法也可以。首先介绍书中的方法,主题对象不将数据传递给观察者,而是让观察者主动获取它需要的数据,这样做有两个好处:

  • 观察者可以获取自己需要的数据,而不像上例中主题把所有的数据都塞给观察者,而CurrentConditionDisplay中只用到了temperature和humidity两个参数。
  • 当WeatherData对象增加一种数据时,不影响观察者。

如果观察者主动拉去自己需要的数据,首先WeatherData对象要提供一系列的get方法,以供观察者获取数据,同时,观察者需要主题的引用,这个应用可以通过update方法进行传递,也可以在观察者注册是保存:

public void update(Subject subject) {
        this.temperature = ((WeatherData)subject).getTemperature();
        this.humidity = ((WeatherData)subject).getHumidity();
        display();
    }

还有一种方法就是将update方法的参数改为Map<String,Object>类型,WeatherData对象定义一系列的key,用于存放不同的数据,布告板自己从中取出所需要的值即可:

public void update(Map<String,Object> data) {
        this.temperature = (float) data.get("temperature");
        this.humidity = (float) data.get("humidity");
        display();
}

4 java内置的Observer API

java给我们提供了一套观察者模式的api,位于util包中,其中主题类为Observable,观察者接口为Observer。
对于数据的获取,java提供了两种模式,一种是主题将数据推给观察者,一种是观察者自己从主题拉去数据。
对于Observer接口很简单,接受两个参数:

public interface Observer {
    void update(Observable o, Object arg);
}

主要是主题类Observable,从这里可以看到代码的健壮性,该类中考虑的东西更全面。
首先,比如addObserver方法:

public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

考虑传入的Observer为null或一个Observer重复注册的问题。
对于nofityObservers方法:

public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

首先根据changed成员变量判断数据是否发生变化,数据发生变化才通知观察者。为什么要有这个成员变量?就拿WeatherData对象来说,如果WeatherData对气候变化很灵敏,那么测出的气候数据是一直在变化的,这样会导致Observer对象会不断收到消息。有个changed成员变量,就可以灵活的控制,比如当气温上升超过1度时才将changed值设置为true。且对该变量的修改是加锁实现的,因为可能有多个线程同时修改该变量,会出现脏读,出现重复update或不能update的情况。
第二个是arrLocal变量,可以看到,通知观察者的for循环是没有加锁的,这部分也确实不应该加锁,因为观察者可能有很多,会导致这一段代码的执行时间很长。更重要的是,你无法确定update方法里的操作是否是非常耗时操作。这样就会带来一个问题,在进行for循环的时候,可能有观察者注册或注销,那怎么判断这个观察者是否需要被通知到呢?在数据发生改变前注销的观察者都不用通知,在数据发生变化后注册的观察者不通知。这也就是arrLocal对象的作用所在了。
当然,Java内置的Observer API也有一些缺陷:

  • Observable是一个具体的实现类,而不是接口,如果你的类已经实现了一个类,就无法使用它了
  • setChanged和clearChanged方法都是protected的,这样就不能通过集合来完成观察者模式

在实际应用中,大部分情况下可能会自己写一套观察者模式,因为这并不困难,并且可以做一些符合实际场景的改动,比如方法名,传参等,使其更加易用易懂。

posted @ 2019-12-15 17:22  leon_x  阅读(62)  评论(0)    收藏  举报