行为型模式
解决的问题
对象之间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。
这里讲的是"对象的状态改变",对象的状态就是封装在对象内部的字段值。
当对象的状态变化时,理论上应该是调用了对象的 set 方法,此时要去通知"依赖它的对象"去更新自身。需要关注两个问题:如何维护"依赖它的对象"?如何"通知"?
为什么会有上面这两个问题?因为面向对象编程,是把现实问题建模成协作的对象。将一个系统分割成一系列相互协作的类,有一个常见的副作用:需要维护相关对象间的一致性。
但我们又不希望为了维护一致性而使各个类紧密耦合,这样会降低它们的可重用性。例如,B 对象依赖了 A 对象,如果在 A 的类中创建 B 的类,那么 B 的类只能被 A 使用,不能被复用。
Observer 模式描述了如何建立这种关系,这一模式的关键对象是"目标(subject)和观察者(observer)",一个目标可以有任意数目的依赖于它的观察者,一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者将查询目标以使自身的状态与目标的状态同步。
组成
参与者:
Subject(目标)
- 目标知道它的观察者,可以有任意多个观察者
- 提供注册和删除观察者的方法
Observer(观察者)
- 在目标发生变化时需要获得通知的对象所定义的接口
- 提供一个更新方法
ConcreteSubjet(具体目标)
- 当它的状态发生变化时,向它的各个观察者发出通知
ConcreteObserver(具体观察者)
- 维护一个指向 ConcrectSubject 对象的引用
- 实现 Observer 接口的更新方法,存储有关状态,这些状态应与目标的状态保持一致
当 ConcreteSubjet 发生任何可能导致其状态变化时,它将通知(notify() 方法)它的各个观察者;在得到一个具体目标的改变通知后,ConcreteObserver 对象可向目标对象查询信息,使用这些信息以使它的状态与目标对象的状态保持一致。
应用场景
存在以下任一情况,可以使用观察者模式:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中,以使它们可以各自独立地改变和复用。
- 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
- 当一个对象必须通知其他对象,而它又不能假定它要通知的具体对象,换言之,你不希望这些对象是紧密耦合的
关键词:依赖、通知、改变、松耦合
关于"抽象、建模":
我们时常说要对业务做抽象和建模,实际上主要的就是,从中找出对象(方法、状态),以及对象之间的关联关系(依赖关系、状态变化关系)。
示例代码
JDK 自带 Subjet 和 Observer,不过自从 JDK9 后就被废弃了。JDK 自带的代码很简单,如下:
java
// Observer 只提供一个方法
public interface Observer {
void update(Observable o, Object arg);
}
java
// Subject
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
// 添加 Observer
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);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
// 有两个竞态条件:
// 1、一个新增的 Observer 没有被通知到
// 2、一个被移出的 Observer 被通知了
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
// 因为不知道 Observer update 方法的耗时,所以要放在锁外面执行
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
优点和缺点
1、目标和观察者间的抽象耦合
一个目标所知道的仅仅是,它有一些列的观察者,目标不知道任何一个观察者所属的具体的类。这样允许我们在目标和观察者各自无感知的情况下,单独改变目标和观察者。
2、支持广播通信
目标发送通知不需要指定它的接收者,通知被自动广播给所有已经等级的观察者。
3、频繁的意外更新
因为目标不知道自己有多少个观察者,所以它在每一次改变时,都有可能引起一系列观察者频繁更新,会导致 notify 方法效率降低。
对于这种情况,如果不是"强一致"需求情况下,可以通过"中间组件、中间件"来维护依赖关系,从而实现异步更新。
4、对已删除目标的悬挂引用
删除一个目标时要注意,不能在其观察者中遗留对该目标的悬挂引用。
但一个目标被删除时,让它通知它的观察者将对该目标的引用复位。
总结
Observer 模式用于解决"一个对象状态变化,依赖于它的其他对象同时更新状态"的问题,并且让目标对象和观察者对象之间保持松耦合。