Java 设计模式之观察者模式

模式动机

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

模式定义

观察者模式(Observer Pattern)定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式是一种对象行为型模式

模式结构

角色组成

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

  • Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法 notify()。目标类可以是接口,也可以是抽象类或具体类。
  • ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法 update(),因此又称为抽象观察者。
  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update() 方法。通常在实现时,可以调用具体目标类的 attach() 方法将自己添加到目标类的集合中或通过 detach() 方法将自己从目标类的集合中删除。

结构图

观察者模式结构图

时序图

观察者模式时序图

示例代码

首先,是观察者和具体观察者。Observer 接口充当观察者,ConcreteObserver 类充当具体观察者,完整代码如下所示:

/**
 * 观察者接口
 */
interface Observer {

    /**
     * 更新的方法
     */
    void update();

}
/**
 * 具体的观察者
 */
public class ConcreteObserver implements Observer {

    /**
     * 实现更新方法
     */
    @Override
    public void update() {
        System.out.println("接收到信息,开始进行处理...");
    }

}

然后,是被观察者和具体被观察者。Subject 抽象类充当被观察者,ConcreteSubject 类充当具体被观察者,完整代码如下所示:

/**
 * 被观察者的抽象类
 */
public abstract class Subject {

    /**
     * 定义一个观察者的集合
     */
    private List<Observer> observers = new ArrayList<Observer>();

    /**
     * 增加一个观察者
     */
    public void addObserver(Observer observer) {
        this.observers.add(observer);
    }

    /**
     * 删除一个观察者
     */
    public void deleteObserver(Observer observer) {
        this.observers.remove(observer);
    }

    /**
     * 通知所有观察者
     */
    public void notifyObservers() {
        // 遍历观察者集合,通知每一个观察者更新
        for (Observer observer : observers) {
            observer.update();
        }
    }

}
/**
 * 具体的被观察者
 */
public class ConcreteSubject extends Subject {

    /**
     * 具体的业务
     */
    public void doSomething() {
        super.notifyObservers();
    }

}

最后,是客户端场景类,完整代码如下所示:

public class ObserverClient {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();   // 创建一个被观察者
        Observer observer = new ConcreteObserver();        // 创建一个观察者
        subject.addObserver(observer);                     // 观察者观察被观察者
        subject.doSomething();                             // 观察者开始活动
    }

}

在有些更加复杂的情况下,具体观察者类 ConcreteObserver的update() 方法在执行时需要使用到具体目标类 ConcreteSubject中的状态(属性),因此在 ConcreteObserver 与 ConcreteSubject 之间有时候还存在关联或依赖关系,在 ConcreteObserver 中定义一个 ConcreteSubject 实例,通过该实例获取存储在 ConcreteSubject 中的状态。
如果 ConcreteObserver 的 update() 方法不需要使用到 ConcreteSubject 中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者 ConcreteObserver 和具体目标 ConcreteSubject 之间无须维持对象引用。

JDK 中的观察者模式

观察者模式在 Java 语言中的地位非常重要。在 JDKjava.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 JDK 对观察者模式的支持。JDK 提供的 Observable 类及 Observer 接口结构图如下图所示:
JDK提供的Observable类及Observer接口结构图

Observer 接口

java.util.Observer 接口中只声明一个方法,它充当抽象观察者,其方法声明代码如下所示:

void  update(Observable o, Object arg);

当观察目标的状态发生变化时,该方法将会被调用,在 Observer 的子类中将实现 update() 方法,即具体观察者可以根据需要具有不同的更新行为。当调用观察目标类 Observable 的 notifyObservers() 方法时,将执行观察者类中的 update() 方法。

Observable 类

java.util.Observable 类充当观察目标类,在 Observable 中定义了一个向量 Vector 来存储观察者对象,它所包含的方法及说明见下表:

方法名 方法描述
Observable() 构造方法,实例化 Vector 向量。
addObserver(Observer o) 用于注册新的观察者对象到向量中。
deleteObserver (Observer o) 用于删除向量中的某一个观察者对象。
notifyObservers()和notifyObservers(Object arg) 通知方法,用于在方法内部循环调用向量中每一个观察者的 update() 方法。
deleteObservers() 用于清空向量,即删除向量中所有观察者对象。
setChanged() 该方法被调用后会设置一个 boolean 类型的内部标记变量 changed 的值为 true,表示观察目标对象的状态发生了变化。
clearChanged() 用于将 changed 变量的值设为 false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的 update() 方法。
hasChanged() 用于测试对象状态是否改变。
countObservers() 用于返回向量中观察者的数量。

我们可以直接使用 Observer 接口和 Observable 类来作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,通过使用 JDK 中的 Observer 接口和 Observable 类,可以更加方便地在 Java 语言中应用观察者模式。

模式分析

  • 观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
  • 这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
  • 作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通。

优点

观察者模式的主要优点如下:

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式符合“开闭原则”的要求。

缺点

观察者模式的主要缺点如下:

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用环境

在以下情况下可以使用观察者模式:

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

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

总结

  • 观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。观察者模式是一种对象行为型模式。
  • 观察者模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
  • 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
  • 观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。
  • 在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 Java 语言对观察者模式的支持。

致谢

Java Design Pattern


评论
 上一篇
Java 设计模式之备忘录模式 Java 设计模式之备忘录模式
模式动机每个人都有过后悔的时候,但人生并无后悔药,有些错误一旦发生就无法再挽回,有些人一旦错过就不会再回来,有些话一旦说出口就不可能再收回,这就是人生。为了不后悔,凡事我们都需要三思而后行。说了这么多,大家可能已经晕了,不是在学设计模式吗?
2018-10-18
下一篇 
Java 设计模式之状态模式 Java 设计模式之状态模式
模式动机对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定
2018-10-14
  目录