观察者模式是一种经常使用的设计模式,在软件系统中对象并不是孤立存在的,一个对象行为的改变可能会导致其他与之存在依赖关系的对象行为发生改变,观察者模式用于描述对象之间的依赖关系。
模式动机
很多情况下,对象不是孤立存在的,想象这么一个场景:你和女朋友去旅行,晚上回到宾馆,女朋友穿着厚厚的大衣,从外表看上去就是个臃肿的包子。你没有反应,等到女朋友洗完澡裹着浴巾出来以后,你立马眼睛都瞪直了,活脱脱一个大色狼(不…不要盯着人家看了啦)
从例子中,我们不难分离出两类角色,一类为观察者,如色眯眯的你,另一类就是被观察者所观察的目标,如性感的女朋友。如果观察目标发生某个动作,观察者就会有响应。
建立一种对象与对象之间的依赖关系,一个对象发生改变时会自动通知其他对象,其他对象将相应做出反应。发生改变的对象称为观察目标,被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且观察者之间没有相互联系,可以根据需要增加和删除观察者,这就是观察者模式的模式动机。
模式定义
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并自动更新。观察者模式又叫发布-订阅模式(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)或从属者模式(Dependents)模式。观察者模式是一种对象行为型模式。
模式分析
观察者模式描述了如何建立对象与对象之间的依赖关系,那么具体如何构造呢?
这一模式的关键对象是观察目标和观察者,一个目标可以有任意多个与之相依赖的观察者,一旦目标状态发生变化,所有观察者都将得到通知。
作为对这个通知的响应,每个观察者都将即时更新自己的状态,与目标状态同步,这种交互也称发布-订阅(publish-subscribe)。
目标是通知的发布者,它发出通知时不需要知道谁是它的观察者,可以有任意数目的观察订阅并接收通知。
由此看一下根据该模式得出的类图
抽象目标 Subject,典型代码如下
import java.util.*;
public abstract class Subject {
// 定义一个集合存储任意数量的观察者对象
protected ArrayList<Observer> observers = new ArrayList<Observer>();
// 增加一个观察者
public abstract void attach(Observer observer);
// 删除一个观察者
public abstract void detach(Observer observer);
// 通知各个观察者并调用它们的 update() 方法
public abstract void notifyObservers();
}
具体目标类 ConcreteSubject 是实现 Subject 的一个具体子类,典型代码如下
public class ConcreteSubject extends Subject {
// 向集合中添加一个观察者
public void attach(Observer observer) {
observers.add(observer);
}
// 从集合中删除一个观察者
public void detach(Observer observer) {
observers.remove(observer);
}
// 循环调用集合中观察者的 updates() 方法
public void notifyObservers() {
for (Observer obs : observers) {
obs.update();
}
}
}
也可以把 attach() 和 detach() 方法的实现放在 Subject 中,这样就无需每个子类都去实现一次了(如果没有特别需求的话)。另外,它还可以实现在目标类中定义的抽象业务逻辑方法(如果有的话)
抽象观察者一般定义为一个接口,其中声明 update() 方法,这个方法在其子类中实现,不同的观察者具有不同的更新响应方法,典型代码如下
public interface Observer {
public void update();
}
具体观察者 ConcreteObserver 中实现 update() 方法,其典型代码如下
public class ConcreteObserver implements Observer {
public void update() {
// 具体更新代码
}
}
在使用时,客户端首先创建具体目标对象以及具体观察者对象(也可以使用配置文件),然后,调用目标对象的 attach() 方法,将这个观察者对象在目标对象中登记,也就是将它加入到目标对象的观察者集合中
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer);
subject.notifyObservers();
客户端调用目标对象的 notifyObservers() 方法时,将调用在其观察者集合中注册的观察者对象的 update() 方法,实现状态的更新
当然,在有些复杂的情况下,具体观察者类 ConcreteObserver 的 update() 方法在执行时,需要使用到 ConcreteSubject 中的状态(属性)
举个例子,我们经常会用可视化的图表(如柱状图、饼状图)来显示数据,同样的数据可能有不同的图表显示方法(即不同的具体观察者),如果数据发生改变,图标也应该实时做出改变,那自然的,图表在更新时肯定需要用到新的数据吧
如果像上述而言,那么 ConcreteObserver 和 ConcreteSubject 之间还存在关联关系,在 ConcreteObserver 中定义一个 ConcreteSubject 实例,通过该实例获取存储在 ConcreteSubject 中的状态属性。但这样一来,系统的扩展性将受到一定影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则,但如果原有观察者无须关联新增具体目标,则系统扩展性不受影响
// 具体目标类
public class ConcreteSubject extends Subject {
// 方便起见,attahch() 等抽象方法在抽象层做了实现了
// 具体目标类状态
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
// 具体观察者类
public class ConcreteObserver implements Observer {
private Subject subject = new ConcreteSubject();
private int observerState;
public void update() {
observerState = subject.getState();
// 具体更新代码
}
}
模式优缺点
观察者模式的优点
- 在观察目标和观察者之间建立一个抽象的耦合,观察目标不需要了解其具体观察者,只需知道它们都有一个共同的接口即可
- 观察者模式支持广播通信,观察目标会向所有注册的观察者发出通知,简化一对多系统设计的难度
- 观察者模式符合开闭原则,增加新的观察者无须修改原有系统代码,在具体观察者和观察目标之间不存在关联关系的情况下,增加新的目标也很方便
观察者模式的缺点
- 如果一个观察目标有很多直接和间接的观察者,将所有观察者都通知到会花费很多时间
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间的循环调用,可能导致系统崩溃
- 观察者模式没有相应机制让观察者知道所观察目标是怎么发生变化的,而仅仅只是知道观察目标发生了变化
Java 对观察者模式的支持
Java 提供了 Observable 类以及 Observer 接口,构成对 Java 语言对观察者模式的支持
java.util.Observer 接口只定义一个方法,充当抽象观察者
public interface Observer {
void update(Observable o, Object arg);
}
当观察目标状态发生变化时,该方法将被调用,在 Observer 的实现子类实现该 update() 方法,即具体观察者可以根据需要有不同的更新行为。当调用观察目标类 Observable 的 notifyObservers() 方法时,将调用观察者类中的 update() 方法。
java.util.Observable 类充当观察目标类,定义了一个向量 Vector 来存储观察者对象。标记变量 changed 表示观察目标是否发生变化,true 表示发生变化,false 则表示对象不再发生改变还已经通知了所有观察者对象,并调用了它们的 update() 方法。
我们可以直接使用 Observer 接口和 Observable 类来作为观察者模式的抽象层,自定义具体的观察者类和观察目标类,更加方便地在 Java 语言中使用观察者模式。