模式定义

观察者模式是对象行为模式,又叫发布-订阅模式(publish/subscribe)模式,源-监视器模式(source/Listener)模式。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够做出相应的操作。

模式结构

软件系统中,常常因为一个对象的状态发生改变时,某些其他对象做出相应的改变,做到这一点的设计方案有很多,但为了系统稳定性,选择低耦合的观察者模式,有助于系统的复用,保证高度协作。

模式描述

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

角色

结构图

  • 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList)里,每一个主题都可以有任何数量的观察者。抽象主题提供一个接口(抽象类),可以增加删除观察者,抽象主题角色又叫抽象被观察者(Observable)角色。
  • 具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登录订阅过的观察者发送通知。具体主题对象又叫具体被观察者(Concrete Observable)角色。
  • 抽象观察者(Observer)角色:给所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
  • 具体观察者(Concrete Observer)角色:存储与主题的状态自恰的状态,具体观察者实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题相协调,具体观察者可以保持一个指向具体主题对象的引用。

实现代码示例:

抽象主题类

public abstract class Subject {
    /**
     * 用来保存注册的观察者对象
     */
    private    List<Observer> list = new ArrayList<Observer>();
    /**
     * 注册观察者对象
     * @param observer    观察者对象
     */
    public void attach(Observer observer){        
        list.add(observer);
        System.out.println("Attached an observer");
    }
    /**
     * 删除观察者对象
     * @param observer    观察者对象
     */
    public void detach(Observer observer){  
        list.remove(observer);
    }
    /**
     * 通知所有注册的观察者对象
     */
    public void nodifyObservers(String newState){
        for(Observer observer : list){
            //推模式下,需要传入状态
            //拉模式下,不需要传入状态
            observer.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);
        //状态发生改变,通知各个观察者
       //在推模式下,需要传入参数
        //拉模式下,不需要传入参数
       this.nodifyObservers(state);//调用父类定义方法   
    }
}

抽象观察者角色类:

public interface Observer {
    /**
     * 更新接口
     * @param state    更新的状态
     */
    public void update(String state);
}

具体观察者角色类:

public class ConcreteObserver implements Observer {
    //观察者的状态
    private String observerState;    
    @Override
    public void update(String state) {
        /**
         * 更新观察者的状态,使其与目标的状态保持一致
         */
        observerState = state;
        System.out.println("状态为:"+observerState);
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("new state");
    }
}

推模式和拉模式

推模式

上面的例子就是典型的推模式,主题对象向观察者推送详细信息。

拉模式

主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

调用观察者的方法时,不需要传入参数

观察者模式与订阅模式

观察者模式

观察者注册到目标;目标发生改变时,调用观察者方法
发布-订阅模式
订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码

总结

  1. 从两张图片可以看到,最大的区别是调度的地方。
    虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。
  2. 两种模式都可以用于松散耦合,改进代码管理和潜在的复用。