观察者模式(Observer Pattern),其实这个模式的应用随着微服务的架构现在基本都用消息队列来取代,但是不影响在一个应用程序中,对于对象间的依赖关系,也有很多的应用,至于核心思想都是一样的,就是一种「发布-订阅」的模式。
观察者模式属于是一个行为型模式之一,其余十个分别为:
- 职责链模式-Chain of Responsibility Pattern
- 命令模式-Command Pattern
- 解释器模式-Interpreter Pattern
- 迭代器模式-Iterator Pattern
- 中介者模式-Mediator Pattern
- 备忘录模式-Memento Pattern
- 状态模式-State Pattern
- 策略模式-Strategy Pattern
- 模板方法模式-Template Method Pattern
- 访问者模式-Visitor Pattern
其中状态模式、策略模式、模板方法模式老四之前已经写过相关文章,可参考文末相关阅读。
观察者模式定义
观察者模式定义了对象之间的一种一对多依赖关系,每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
正如定义所描述一样,我们之前使用的 MVC 其实也是一种观察者模式的实现,目的就是为了解除耦合,符合「开闭」原则。
观察者模式的结构及其相关角色
- Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法 notify。目标类可以是接口,也可以是抽象类或具体类。
- ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
- Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法 update,因此又称为抽象观察者。
- ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update 方法。通常在实现时,可以调用具体目标类的 attach 方法将自己添加到目标类的集合中或通过 detach 方法将自己从目标类的集合中删除。
根据观察者模式的结构,我们的代码其实就已经很清晰了。主要结构示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package learn.design.patterns.observer; /** * 观察者 * * @author : 高老四 * @InterfaceName : Observer * @since : 2021/7/11 00:43 */ public interface Observer { /** * 观察者接到通知后执行的业务逻辑 * @Title : update * @return : void */ void update(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package learn.design.patterns.observer; /** * 具体的观察者 * * @author : 高老四 * @ClassName : ConcreteObserver * @since : 2021/7/11 00:51 */ public class ConcreteObserver implements Observer { @Override public void update() { System.out.println("接收到变化后,我要执行自己的业务逻辑 glorze.com"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package learn.design.patterns.observer; import java.util.ArrayList; import java.util.List; /** * 主题目标接口 * * 被观察者 * * @author : 高老四 * @InterfaceName : Subject * @since : 2021/7/11 00:42 */ public interface Subject { List<Observer> observerList = new ArrayList<>(); void attach(Observer observer); void detach(Observer observer); void notifyObserver(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package learn.design.patterns.observer; /** * 具体的目标主题,被观察者,维护观察者列表 * * @author : 高老四 * @ClassName : ConcreteSubject * @since : 2021/7/11 00:47 */ public class ConcreteSubject implements Subject { @Override public void attach(Observer observer) { observerList.add(observer); } @Override public void detach(Observer observer) { observerList.remove(observer); } @Override public void notifyObserver() { observerList.forEach(Observer::update); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package learn.design.patterns.observer; /** * 运行 * * @author : 高老四 * @ClassName : ObserverPatternMain * @since : 2021/7/11 00:53 */ public class ObserverPatternMain { public static void main(String[] args) { Observer observer1 = new ConcreteObserver(); Observer observer2 = new ConcreteObserver(); Observer observer3 = new ConcreteObserver(); Observer observer4 = new ConcreteObserver(); Subject subject = new ConcreteSubject(); subject.attach(observer1); subject.attach(observer2); subject.attach(observer3); subject.attach(observer4); subject.notifyObserver(); } } |
如上代码所示,就是观察者模式的核心结构了,实现了对象间一对多的依赖,使得耦合的双方均依赖于抽象,各自的变化不会影响另一边的变化。
观察者模式的优势
- 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
- 观察者模式满足「开闭原则」的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
观察者模式的缺点
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象,可以使用观察者模式创建一种链式触发机制。
其实 JDK 中就内置了观察者模式,提供了 Observer 接口和 Observable 类让我们简单快速的实现观察者模式。
如图所示,这是观察者抽象接口,声明了一个 update 方法,用于接收通知者的通知做出相应改变,其中第一个 Observable 类型的参数是被观察者的引用,当需要与被观察者进行交互的时候,就需要这个引用,另一个 Object 类型的参数是被观察者传递过来的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
package java.util; /** * 被观察者 * * @author Chris Warth * @see java.util.Observable#notifyObservers() * @see java.util.Observable#notifyObservers(java.lang.Object) * @see java.util.Observer * @see java.util.Observer#update(java.util.Observable, java.lang.Object) * @since JDK1.0 */ public class Observable { /** * 用来标志是否变化,默认无变化 */ private boolean changed = false; /** * 存储观察者对象 */ private Vector<Observer> obs; /** * 空构造方法 */ public Observable() { obs = new Vector<>(); } /** * 增加观察者 * * @param o 增加的对象 * @throws NullPointerException if the parameter o is null. */ public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } /** * 删除观察者 * @param o 被删除的对象 */ public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } /** * 通知所有观察者更新 * * @see java.util.Observable#clearChanged() * @see java.util.Observable#hasChanged() * @see java.util.Observer#update(java.util.Observable, java.lang.Object) */ public void notifyObservers() { notifyObservers(null); } /** * 知所有观察者更新 * * @param arg 通知内容 * @see java.util.Observable#clearChanged() * @see java.util.Observable#hasChanged() * @see java.util.Observer#update(java.util.Observable, java.lang.Object) */ public void notifyObservers(Object arg) { /* * a temporary array buffer, used as a snapshot of the state of * current Observers. */ Object[] arrLocal; synchronized (this) { /* 对 change 加同步锁,防止多线程下各线程对 changed 变量的读写操作不安全 * 可能出现脏读因此产生重复 update 或者不能 update 的情况 */ if (!changed) { // 如果没有变化,直接返回 return; } arrLocal = obs.toArray(); // 重置变化 clearChanged(); } // 循环通知更新 for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } /** * 删除所有观察者 */ public synchronized void deleteObservers() { obs.removeAllElements(); } /** * 设置变化,调用后 changed 为 true */ protected synchronized void setChanged() { changed = true; } /** * 清除变化,调用后 changed 为 false * * @see java.util.Observable#notifyObservers() * @see java.util.Observable#notifyObservers(java.lang.Object) */ protected synchronized void clearChanged() { changed = false; } /** * 是否变化 * * @see java.util.Observable#clearChanged() * @see java.util.Observable#setChanged() */ public synchronized boolean hasChanged() { return changed; } /** * 统计观察者数量然后返回 */ public synchronized int countObservers() { return obs.size(); } } |
被观察者源代码如上所示,代码很专业,加了线程安全的一些操作。这里面有两个方法比较重要:
- setChange():设置一个内部标志位注明数据发生了变化
- notifyObservers():调用一个列表中所有的 Observer 的 update() 方法
所有方法都是同步的,保证了在一个线程对其标志位、列表进行操作时,不会有其它线程也在操作它。
注意:观察者模式在 Java 9 中弃用了这两个类,原因是:
- 不能序列化:Observable 没有实现 Serializable 接口,它的内部成员变量都是私有的,子类不能通过继承它来对 Observable 的成员变量处理。所以子类也不能序列化。
- 不是线程安全:虽然 Observable 保证了同步,但是它允许子类覆盖重写 Observable 的方法,事件通知无序以及事件通知发生在不同的线程里,这些都是会影响线程安全的问题。
- 支持事件模型的功能简单:只是支持事情发生变化的概念,对于复杂的业务模型,仅仅一个变化通知对象是不够的的。
可以使用java.beans 里的 PropertyChangeEvent 和 PropertyChangeListener 来代替目前 Observer 和 Observable 的功能。
Spring 中对观察者设计模式的使用
在前后端未分离之前,我们所熟悉的 MVC 模式其实也可以理解为是「观察者」设计模式的一种,观察目标就是 MVC 中的模型(Model),而观察者就是 MVC中 的视图(View),控制器(Controller) 充当两者之间的中介者。当模型层的数据发生改变时,视图层将自动改变其显示内容。
在 Spring 中,事件的监听就是观察者设计模式的体现:
-
ApplicationEvent:所有事件对象的父类。ApplicationEvent 继承自 jdk 的 EventObject,,所有的事件都需要继承 ApplicationEvent, 并且通过 source 得到事件源。
-
ApplicationListener:观察者,继承自 jdk 的 EventListener,该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。
-
ApplicationContext:Spring 中的核心容器,在事件监听中 ApplicationContext 可以作为事件的发布者,也就是事件源。ApplicationContext 继承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定义了事件发布的方法:publishEvent(Object) event
-
ApplicationEventMulticaster:事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。
相关文章阅读
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可以加入本站封闭式交流论坛「DownHub」开启新世界的大门,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。