一、描述

  1. 别名:
    1. 发布(Publish)-订阅(Subscribe)模式;
    2. 模型(Model)-视图(View)模式;
    3. 源 - 收听者(Listener)模式。
  2. 功能概述:
    一个被观察者,管理所有依赖于它的多个观察者,并且在被观察者本身状态发生改变时候通知所有观察者。是一种一对多的关联关系引用,通常被用于事件处理系统。

二、模式结构图

observer_pattern.svg

三、角色解析

  1. 抽象观察者 Observer
    为所有具体的观察者定义一个接口,在收到主题通知时候更新自己。
  2. 抽象被观察者 Subject
    把所有观察者对象的引用保存到一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色
  3. 具体的被观察者 Concrete Subject
    具体的主题,在所有内部主题发生改变时候,向所有登记的观察者对象发出通知。
  4. 具体的观察者 Concrete Observer
    实现抽象观察者所需的更新方法,使本身的状态与视图状态一致。

四、具体实现

此处不再解析具体实现,详细见代码注释

五、JDK实现观察者模式分析

JDK中,java.util.Observable 抽象类和 java.util.Observer 接口,分别是JDK提供的抽象被观察者和抽象观察者。我们进行使用的时候,继承或实现他们,并重写相应方法即可。

抽象观察者 Observer

  1. package java.util;
  2. public interface Observer {
  3. void update(Observable o, Object arg);
  4. }

此接口很简单,提供一个更新消息的方法。两个参数

  1. Observable ==> 发通知的被观察者。
  2. Object ==> 通知的内容。

抽象被观察者 Observable

标记是否改变状态的属性

  1. public class Main {
  2. private boolean changed = false;
  3. protected synchronized void setChanged() {
  4. changed = true;
  5. }
  6. protected synchronized void clearChanged() {
  7. changed = false;
  8. }
  9. public synchronized boolean hasChanged() {
  10. return changed;
  11. }
  12. }

添加和移除观察者,有判断

  1. public class Main {
  2. // 采用线程安全的Vector
  3. private Vector<Observer> obs;
  4. public Observable() {
  5. // 构造时候创建数组
  6. obs = new Vector<>();
  7. }
  8. public synchronized void addObserver(Observer o) {
  9. // 判空
  10. if (o == null) {
  11. throw new NullPointerException();
  12. }
  13. // 判断重复
  14. if (!obs.contains(o)) {
  15. obs.addElement(o);
  16. }
  17. }
  18. public synchronized void deleteObserver(Observer o) {
  19. obs.removeElement(o);
  20. }
  21. }

通知观察者的方法

  1. public class Main {
  2. public void notifyObservers() {
  3. notifyObservers(null);
  4. }
  5. public void notifyObservers(Object arg) {
  6. /*
  7. * a temporary array buffer, used as a snapshot of the state of
  8. * current Observers.
  9. */
  10. // 方法内部,存放观察者对象的数组
  11. Object[] arrLocal;
  12. // 对象锁,用于
  13. // 1. 判断对象的状态
  14. // 2. 拿到观察者的快照并放到 arrLocal数组
  15. synchronized (this) {
  16. /* We don't want the Observer doing callbacks into
  17. * arbitrary code while holding its own Monitor.
  18. * The code where we extract each Observable from
  19. * the Vector and store the state of the Observer
  20. * needs synchronization, but notifying observers
  21. * does not (should not). The worst result of any
  22. * potential race-condition here is that:
  23. * 1) a newly-added Observer will miss a
  24. * notification in progress
  25. * 2) a recently unregistered Observer will be
  26. * wrongly notified when it doesn't care
  27. */
  28. if (!changed) {
  29. // 未改变则直接退出
  30. return;
  31. }
  32. // 得到快照,在同步块中,线程安全
  33. arrLocal = obs.toArray();
  34. // 清除变化状态
  35. clearChanged();
  36. }
  37. // 对快照数组逆序遍历通知
  38. for (int i = arrLocal.length-1; i>=0; i--) {
  39. ((Observer) arrLocal[i]).update(this, arg);
  40. }
  41. }
  42. }

重点分析带参的通知观察者的方法
  1. 若对方法加锁,则观察者数量较多的时候,依次进行遍历通知将是一个非常耗时且消耗资源的操作。
  2. 方法中的快照数组 arrLocal是线程安全的(方法存在于栈中)。将观察者属性数组转换为快照数组的时候,在同步块中,是线程安全的。
  3. arrLocal数组缓存的作用体现在,如果一个线程添加观察者,一个线程删除,则会产生并发修改异常ConcurrentModificationException,有可能出现空指针异常。
  4. 缓存机制的缺点。
    场景举例
    1. 线程1:调用notifyObservers() 方法通知。
    2. 线程2:删除观察者或添加观察者。

一方面,新添加的不会立刻得到通知(除非添加完立刻又线程执行obs.toArray()方法)。
另一方面,新删除的不会立刻停止通知。设计上不太好避免的问题。

  1. Vector是一个线程安全的集合类,在进行toArray()方法的时候其他线程无法访问obs。

观察者模式的优势和不足

优势

  1. 目标和观察者之间的抽象耦合
    目标仅知道自己有一系列的观察者实现同一个接口,并不知道观察者属于哪个具体的类,这样的耦合是最小的。
  2. 支持广播通信
    向注册的所有观察者通知,而不是特定的观察者,给了增加和删除观察者的自由。对通知的处理取决于观察者。

劣势

  1. 意外的更新
    由于无法知晓观察者的存在,对改变目标的代价一无所知。在目标对象的操作可能会引起一系列对观察者以及依赖于观察者对象的更新。若以来准则定义或维护不当,常常引起难以捕捉的错误更新。
    简单的更新协议不提供具体的细节说明目标中什么被改变了,就使得问题更加严重。

实现的细节

创建目标到观察者之间的映射

目标很多而观察者较少时候,目标中保存引用的存储代价太高。

解决办法是,创建一个关联查找机制来维护。

  1. 优势:没有观察者的目标不产生开销。
  2. 劣势:增加了访问观察者的开销。

观察多个目标

某些情况下,一个观察者观察多个目标。必须扩展update接口,将目标对象作为参数,让观察者知道发送通知的目标是谁。

  1. public interface Main {
  2. public void update(Subject s, Message msg);
  3. }

谁触发更新

两个选择:

  1. 目标对象改变后自动调用通知方法。
    • 优势:不需要客户操作;
    • 劣势:多个操作可能产生多个连续的更新,效率低。
  2. 客户负责在适当时候调用notify方法。
    • 优势:避免中间不必要的更新,效率提升。
    • 劣势:客户调用,容易出错。

对已删除目标的悬挂引用

删除一个目标时,不应该在其所有观察者中遗留该目标对象的悬挂引用。一种做法是,在删除时候通知该观察者的引用复位。一般来说,不能简单的删除观察者,其他对象可能引用他们,或者他们本身在观察其他目标。

发通知前确保状态一致

避免特定于观察者的更新协议——推 / 拉 模型

常常需要实现传播目标改变的详细信息。目标可以将详细信息作为update的参数传递出去。极端的情况是:

  1. 推模型:目标始终发送详细信息,不管观察者是否需要;
  2. 拉模型:目标除了最小信息外什么也不发送。

显式指定感兴趣的改变

扩展目标的注册接口,指定感兴趣的改变,以提高更新效率。

  1. public interface Main {
  2. void attach(Observer o, Aspect interest);
  3. }

同时在更新时候,将这些兴趣方面的改变作为参数传递。

  1. public interface Main {
  2. void update(Subject s, Aspect interest);
  3. }

复杂的更新语义

当目标和观察者之间的依赖关系特别复杂时,可能需要一个维护这些关系的对象,我们将这些对象称为更改管理器(Change Manger)。其功能有三:

  1. 将目标维护一个映射到其观察者,并提供一个接口来维护这个映射。解除目标来维护与其观察者关系引用的耦合。
  2. 定义特定的更新策略。
  3. 根据一个目标的请求,它更新所有依赖于这个目标的观察者。

基于关系更改管理器的类图如下:

observer_managedRelation.svg

六、服务器客户端案例

1. 抽象被观察者

  • 注册;
  • 移除;
  • 通知;
  1. public interface Subject {
  2. void registerObserver(Observer o);
  3. void removeObserver(Observer o);
  4. void notifyObservers();
  5. }

2. 抽象观察者

  • 接受通知的回调方法
  1. public interface Observer {
  2. void update(String message);
  3. }

3. 具体的被观察主题

  • 持有观察者的引用;
  • 在主题内部发生改变的时候,向注册的观察者发出消息。
  1. public class WechatServer implements Subject {
  2. private final List<Observer> observerList;
  3. private String message;
  4. public WechatServer() {
  5. observerList = new ArrayList<>();
  6. }
  7. @Override
  8. public void registerObserver(Observer o) {
  9. if (!observerList.contains(o)) {
  10. observerList.add(o);
  11. }
  12. }
  13. @Override
  14. public void removeObserver(Observer o) {
  15. if (!observerList.isEmpty()) {
  16. observerList.remove(o);
  17. }
  18. }
  19. @Override
  20. public void notifyObservers() {
  21. for (Observer observer : observerList) {
  22. // 回调
  23. observer.update(message);
  24. }
  25. }
  26. /**
  27. * 设置消息推送
  28. *
  29. * @param message 要推送的消息
  30. */
  31. public void setMessage(String message) {
  32. this.message = message;
  33. System.out.println("微信服务更新消息:" + message);
  34. notifyObservers();
  35. }
  36. }

4. 具体的观察者

  • 实现所需更新方法。
  1. public class ConcreteObserver implements Observer {
  2. private final String name;
  3. private String message;
  4. public ConcreteObserver(String name) {
  5. this.name = name;
  6. }
  7. @Override
  8. public void update(String message) {
  9. this.message = message;
  10. read();
  11. }
  12. public void read() {
  13. System.out.println(name + "收到推送消息:" + message);
  14. }
  15. public static void main(String[] args) {
  16. WechatServer ws = new WechatServer();
  17. Observer o1 = new ConcreteObserver("o1");
  18. Observer o2 = new ConcreteObserver("o2");
  19. Observer o3 = new ConcreteObserver("o3");
  20. ws.registerObserver(o1);
  21. ws.registerObserver(o2);
  22. ws.registerObserver(o3);
  23. ws.setMessage("PHP NB!");
  24. System.out.println("_____________________");
  25. ws.removeObserver(o2);
  26. ws.setMessage("Java !");
  27. }
  28. }