观察者模式

什么是观察者模式?

概念:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

说白了就是一个或多个观察者同时可以观察一个被观察者,当被观察者发生变化,所有的观察者都会收到通知,做相应的工作。一个最简单的例子,多个警察盯上一个小偷,在小偷进行偷窃行为时,警察们收到信号实施抓捕。

为什么要用观察者模式?

当我们在一个模块中监听另一个模块的事件,而又不想让它们之间有依赖性,就可以选择观察者模式。

简单实现

例如公众号不时推送一些优秀文章,只要关注了它就可以收到推送,这里我们用户是观察者,公众号就是被观察者。

/**
 * 观察者
 */
public class User implements Observer {
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        // 收到事件通知
        System.out.println("Hi," + name + ", 推荐一篇优秀文章, 内容:" + arg);
    }
}
/**
 * 被观察者
 */
public class JianShuSubscription extends Observable {

    public void pushArticle(String content){
        // 标识状态或者内容发生改变
        setChanged();
        // 通知所有观察者
        notifyObservers(content);
    }
}
public class Test {
    public static void main(String[] args){
        // 新建被观察者
        JianShuSubscription jianShuSubscription = new JianShuSubscription();
        // 观察者
        User a = new User("A");
        User b = new User("B");
        User c = new User("C");
        User d = new User("D");
        
        // 将观察者注册到可观察对象的观察者列表中
        jianShuSubscription.addObserver(a);
        jianShuSubscription.addObserver(b);
        // 重复注册
        jianShuSubscription.addObserver(b);
        jianShuSubscription.addObserver(c);
        jianShuSubscription.addObserver(d);
        
        // 发布消息
        jianShuSubscription.pushArticle("观察者模式");
    }
}

运行结果:

观察者模式.png

可见重复注册也只会通知一次,我们来看看java.util包下的这两个类Observer和Observable:

package java.util;

/**
 * @author  Chris Warth
 * @see     java.util.Observable
 * @since   JDK1.0
 */
public interface Observer {
    /**
     * Observable调用notifyObservers()方法时被调用
     * @param   o     被观察的对象
     * @param   arg   传过来的信息
     */
    void update(Observable o, Object arg);
}

Observer类中只有一个update方法,在Observable调用notifyObservers()方法时被调用,那我们看一下Observable中的notifyObservers()的方法实现。

package java.util;


public class Observable {
    // 是否被改变标识
    private boolean changed = false;
    // 观察者集合
    private Vector obs;

    public Observable() {
        obs = new Vector<>();
    }

    /**
     * 添加
     * @param o
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        // 防止观察者重复
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 删除
     * @param o
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * 通知所有的观察者,不携带信息
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 通知所有的观察者,携带信息arg
     */
    public void notifyObservers(Object arg) {
        // 临时缓冲区,保存当时的观察者集合
        Object[] arrLocal;

        // 这里加锁显然是为了线程安全,但是可能会有2种不希望的结果
        // 1、最新添加的观察者无法接收到这个通知
        // 2、最近反注册掉的观察者也能接收到这个通知
        synchronized (this) {
            // 判断改变标识
            if (!hasChanged())
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            // 逐一调用观察者的update()方法
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * 清除所有观察者
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 把被观察者标识设为改变状态:我准备发通知了
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 我已经确认发过通知了
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 判断此时是不是正在发通知
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * 观察者数量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

上面注释已经写得很清楚了,被观察者发送通知时需要调用2个方法:

  1. setChanged() 改变标志位,准备发通知。
  2. notifyObservers(content) 通知所有的观察者。

缺点:

观察者模式优点是:观察者和被观察者之间是抽象耦合,没有直接依赖关系;增强系统灵活性、可扩展性。
在应用观察者模式时需要考虑一下开发效率和运行效率问题,程序中包括一个被观察者、多个观察者,开发和调试等内容会比较复杂,而且在Java种消息的通知默认时顺序执行,一个观察者卡顿,会影响整体的运行效率,在这种情况下,要考虑采用异步的方式。

你可能感兴趣的:(观察者模式)