动机

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

定义

观察者模式(Observer Pattern):观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象。这个目标对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。观察者模式又叫做发布-订阅(Publish/Subscribe)模式。是一种行为型设计模式。

结构

观察者模式包含如下角色:

img1.png

代码实现

我们用代码来实现一个观察者模式:

目标类:

//-----------抽象目标类
public abstract class Subject {
	//用来保存注册的观察者
	private List<Observer> list = new ArrayList<>();
	/**
	 * 注册观察者对象
	 * @param observer
	 */
	public void attach(Observer observer){
		list.add(observer);
		System.out.println("增加了一个观察者"+observer.getName());
	}
	/**
	 * 删除观察者对象
	 * @param observer
	 */
	public void detach(Observer observer){
		list.remove(observer);
		System.out.println("删除了一个观察者");
	}
	/**
	 * 通知所有注册的观察者对象
	 * @param newState
	 */
	public void notifyObservers(String newState){
		for(Observer o :list){
			o.update(newState);
		}
	}
}
//-----------具体目标类
public class ConcreteSubject extends Subject{
	private String state;
	
	public String getState(){
		return state;
	}
	public void change(String newState){
		state = newState;
		System.out.println("目标状态为:"+state);
		notifyObservers(state);
	}
}

观察者类:

//----------------抽象观察者
public abstract class Observer {
	//观察者的名字
	protected String name;
	//观察者的状态
	protected String observerState;
	public String getName(){
		return name;
	}
	
	public Observer(String name){
		this.name = name;
	}
	
	/**
	 * 更新状态
	 * @param state
	 */
	public abstract void update(String state);
}
//--------------具体观察者
public class ConcreteObserver extends Observer{
	
	public ConcreteObserver(String name) {
		super(name);
	}
	
	/**
	 * 更新观察者的状态,使其与目标状态一致
	 */
	@Override
	public void update(String state) {
		observerState = state;
		System.out.println("观察者["+getName()+"]状态:"+observerState);
	}
	
}

运行结果为:

增加了一个观察者1号 增加了一个观察者2号 增加了一个观察者3号 目标状态为:新状态 观察者[1号]状态:新状态 观察者[2号]状态:新状态 观察者[3号]状态:新状态

运行时,首先创建了具体目标类的实例,以及 3 个观察者对象。然后,它调用主题对象的 attach() 方法,将这 3 个观察者对象向目标对象登记,也就是将它加入到目标对象的 List 中去。

这时,客户端调用目标的 change() 方法,改变了目标对象的内部状态。目标对象在状态发生变化时,调用超类的 notifyObservers() 方法,通知所有注册过的观察者对象。

推模型和拉模型

观察者又可以分为推模型和拉模型,上面代码实现的就是一个推模型。

拉模型代码实现

抽象目标类:拉模型的抽象目标类主要的改变是 nodifyObservers() 方法。在循环通知观察者的时候,也就是循环调用观察者的 update() 方法的时候,传入的参数不同了。

public abstract class Subject {
	//用来保存注册的观察者
	private List<Observer> list = new ArrayList<>();
	/**
	 * 注册观察者对象
	 * @param observer
	 */
	public void attach(Observer observer){
		list.add(observer);
		System.out.println("增加了一个观察者"+observer.getName());
	}
	/**
	 * 删除观察者对象
	 * @param observer
	 */
	public void detach(Observer observer){
		list.remove(observer);
		System.out.println("删除了一个观察者");
	}
	/**
	 * 通知所有注册的观察者对象
	 * @param newState
	 */
	public void notifyObservers(){
		for(Observer o :list){
			o.update(this);
		}
	}
}

具体目标类:跟推模型相比,有一点变化,就是调用通知观察者的方法的时候,不需要传入参数了。

public class ConcreteSubject extends Subject{
	private String state;
	
	public String getState(){
		return state;
	}
	public void change(String newState){
		state = newState;
		System.out.println("目标状态为:"+state);
		notifyObservers();
	}
}

抽象观察者类:拉模型通常都是把主题对象当做参数传递。

public abstract class Observer {
	//观察者的名字
	protected String name;
	//观察者的状态
	protected String observerState;
	public String getName(){
		return name;
	}
	
	public Observer(String name){
		this.name = name;
	}
	
	/**
	 * 更新状态
	 * @param state
	 */
	public abstract void update(Subject subject);
}

具体观察者

public class ConcreteObserver extends Observer{
	
	public ConcreteObserver(String name) {
		super(name);
	}
	
	/**
	 * 更新观察者的状态,使其与目标状态一致
	 */
	@Override
	public void update(Subject subject) {
		observerState = ((ConcreteSubject)subject).getState();
		System.out.println("观察者["+getName()+"]状态:"+observerState);
	}
	
}
两种模式的比较

JDK 中的观察者模式支持

在 Java 的 java.util 库里面,提供了一个 Observable 类以及一个 Observer 接口,构成对观察者模式的支持。

Observer 接口

这个接口只定义了一个方法,即 update() 方法,当被观察者对象的状态发生变化时,被观察者对象的 notifyObservers() 方法就会调用这一方法。

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

被观察者类都是 java.util.Observable 类的子类。Observable 提供公开的方法支持观察者对象,这些方法中有两个对 Observable 的子类非常重要:一个是 setChanged(),另一个是 notifyObservers()。第一方法 setChanged() 被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是 notifyObservers(),这个方法被调用时,会调用所有注册过的观察者对象的 update() 方法,使这些观察者对象可以更新自己。

public class Observable {
    private boolean changed = false;
    private Vector obs;
   
    /** Construct an Observable with zero Observers. */

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

    /**
     * 将一个观察者添加到观察者聚集上面
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
        	obs.addElement(o);
        }
    }

    /**
     * 将一个观察者从观察者聚集上删除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
    	notifyObservers(null);
    }

    /**
     * 如果本对象有变化(那时hasChanged 方法会返回true)
     * 调用本方法通知所有登记的观察者,即调用它们的update()方法
     * 传入this和arg作为参数
     */
    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);
    }

    /**
     * 将观察者聚集清空
     */
    public synchronized void deleteObservers() {
    	obs.removeAllElements();
    }

    /**
     * 将“已变化”设置为true
     */
    protected synchronized void setChanged() {
    	changed = true;
    }

    /**
     * 将“已变化”重置为false
     */
    protected synchronized void clearChanged() {
    	changed = false;
    }

    /**
     * 检测本对象是否已变化
     */
    public synchronized boolean hasChanged() {
    	return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
    	return obs.size();
    }
}
使用示例

这里给出一个非常简单的例子,说明怎样使用 Java 所提供的对观察者模式的支持。在这个例子中,被观察对象叫做 Watched;而观察者对象叫做 Watcher。Watched 对象继承自 java.util.Observable 类;而 Watcher 对象实现了 java.util.Observer 接口。另外有一个 Test 类扮演客户端角色。

被观察者 Watched 类

public class Watched extends Observable{
    
    private String data = "";
    
    public String getData() {
        return data;
    }

    public void setData(String data) {
        
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }
        notifyObservers();
    }
}

观察者 Watcher 类

public class Watcher implements Observer{
    
    public Watcher(Observable o){
        o.addObserver(this);
    }
    
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("状态发生改变:" + ((Watched)o).getData());
    }

}

测试类

public class Test {

    public static void main(String[] args) {
        //创建被观察者对象
        Watched watched = new Watched();
        //创建观察者对象,并将被观察者对象登记
        Observer watcher = new Watcher(watched);
        //给被观察者状态赋值
        watched.setData("start");
        watched.setData("run");
        watched.setData("stop");
    }
}

首先创建了 Watched 和 Watcher 对象。在创建 Watcher 对象时,将 Watched 对象作为参数传入;然后调用 Watched 对象的 setData() 方法,触发 Watched 对象的内部状态变化;Watched 对象进而通知实现登记过的 Watcher 对象,也就是调用它的 update() 方法。

模式分析

优点
缺点

适用场景

模式应用

观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

在 Android 中应用

Android 中大量的使用了观察者模式,Framework 层里面的事件驱动都是基于观察者模式实现的。另外在 Framework 层里面的各种服务在数据变更的时候,也是通过观察者模式实现上层数据更新的。像 View 的 Listener 监听、GPS 位置信息监听、BroadcastReceiver 等都是基于观察者模式实现的。

ListView 的适配器中有个函数 notifyDataSetChanged(),这个函数其实就是通知 ListView 的每个 Item,数据源发生了变化,让各个 Item 重新刷新一下。这里也用到了观察者模式。

参考资料

《JAVA与模式》之观察者模式

观察者模式

当观察者模式和回调机制遇上 Android 源码