在软件系统中,有些对象之间也存在类似交通信号灯和汽车之间的关系。一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动,正所谓“触一而牵百发”。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式应运而生。它定义了对象之间一对多的依赖关系,让一个对象的改变能够影响其他对象。
观察者模式是使用频率最高的设计模式之一,用于建立对象与对象之间的依赖关系。一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
观察者模式结构中通常包括观察目标和观察者两个继承层次结构,其结构如下图所示:
可以看出,在观察者模式结构图中包含以下4个角色:
观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象。一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。
下面通过示意代码来对该模式进行进一步分析。首先定义一个抽象目标Subject,典型代码如下:
/**
* @Description: 抽象目标
* @Author: yangyongbing
* @CreateTime: 2023/08/03 18:35
* @Version: 1.0
*/
abstract class Subject {
// 定义一个观察者集合用于存储所有观察者对象
protected List<Observer> observers=new ArrayList<>();
// 注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer){
observers.add(observer);
}
// 注销方法,用于在观察者集合中删除一个观察者
public void detach(Observer observer){
observers.remove(observer);
}
// 声明抽象通知方法
public abstract void notify();
}
具体目标类ConcreteSubject是实现抽象目标类Subject的一个具体子类,其典型代码如下:
/**
* @Description: 具体目标
* @Author: yangyongbing
* @CreateTime: 2023/08/03 18:42
* @Version: 1.0
*/
public class ConcreteSubject extends Subject{
// 实现通知方法
@Override
public void notify() {
// 遍历观察者集合,调用每一个观察者的响应方法
for(Object obs:observers){
((Observer)obs).update();
}
}
}
抽象观察者角色一般定义为一个接口,通常只声明一个update()方法,为不同观察者的更新(响应)行为定义相同的接口。这个方法在其子类中实现,不同的观察者具有不同的响应方法。抽象观察者Observer典型代码如下:
/**
* @Description: 抽象观察者
* @Author: yangyongbing
* @CreateTime: 2023/08/03
* @Version: 1.0
*/
public interface Observer {
// 声明响应方法
void update();
}
在具体观察者ConcreteObserver中实现了update()方法,其典型代码如下:
/**
* @Description: 具体观察者
* @Author: yangyongbing
* @CreateTime: 2023/08/03 18:46
* @Version: 1.0
*/
public class ConcreteObserver implements Observer{
// 实现响应方法
@Override
public void update() {
// 具体响应代码
}
}
在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性)。因此,在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系。在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。如果在具体层具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则。但是,如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。
观察者模式在Java语言中的地位非常重要。在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持,如下图所示。
1、Observer接口
在java.util.Observer接口中只声明一个方法,它充当抽象观察者,其方法声明代码如下:
当观察目标的状态发生变化时,该方法将会被调用。在Observer的子类中将实现update()方法,即具体观察者可以根据需要具有不同的更新行为。当调用观察目标类Observable的notifyObservers()方法时,将执行观察者类中的update()方法。
2、Observable类
java.util.Observable类充当观察目标类。在Observable中定义了一个向量Vector来存储观察者对象,它所包含的方法及说明如下图所示。
可以直接使用Observer接口和Observable类来作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。通过使用JDK中的Observer接口和Observable类,可以更加方便地在Java语言中应用观察者模式。
在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式。MVC是一种架构模式,它包含3个角色:模型(Model)、视图(View)和控制器(Controller)。其中,模型可对应于观察者模式中的观察目标,而视图对应于观察者,控制器可充当两者之间的中介者。当模型层的数据发生改变时,视图层将自动改变其显示内容,如下图所示。
模型层提供的数据是视图层所观察的对象。在视图层中包含两个用于显示数据的图表对象,一个是柱状图,一个是饼状图,相同的数据拥有不同的图表显示方式。如果模型层的数据发生改变,两个图表对象将随之发生变化,这意味着图表对象依赖模型层提供的数据对象,因此数据对象的任何状态改变都应立即通知它们。同时,这两个图表之间相互独立,不存在任何联系,而且图表对象的个数没有任何限制,用户可以根据需要再增加新的图表对象,例如折线图。在增加新的图表对象时,无须修改原有类库,满足开闭原则。
观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在。它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及一对一或者一对多的对象交互场景都可以使用观察者模式。观察者模式广泛应用于各种编程语言的GUI事件处理的实现,在基于事件的XML解析技术(例如SAX2)以及Web事件处理中也都使用了观察者模式。