文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【十九、观察者模式】

一、观察者模式介绍

在软件系统中,经常存在这样的场景:一个对象(目标对象)的状态发生改变,需要通知到其他多个对象(观察者对象),并让它们做出相应的响应。例如,用户点击一个按钮(目标),需要通知事件处理器(观察者);商品价格发生变化(目标),需要通知所有关注该商品的用户(观察者)。

如果让目标对象直接持有所有观察者对象的引用并调用它们的方法,会导致:

  1. 目标对象与观察者对象高度耦合: 目标对象需要知道所有具体的观察者。
  2. 难以动态添加或删除观察者: 需要在目标对象内部修改代码。
  3. 违反开闭原则: 新增观察者类型需要修改目标对象的代码。

观察者模式通过引入一个抽象的“通知”层,完美地解决了上述问题。它让目标对象只依赖于观察者的抽象接口,从而实现了两者之间的松耦合。

二、核心概念与意图

  1. 核心概念

    • 主题/目标 (Subject): 也被称为“被观察者”(Observable)。它维护一个观察者列表,并提供添加(attach)、删除(detach)观察者的方法。它知道当自身状态改变时,需要通知哪些观察者。
    • 具体主题/具体目标 (Concrete Subject): 实现主题接口。当它的状态发生改变时,会遍历其观察者列表,调用每个观察者的更新方法。
    • 观察者 (Observer): 定义一个更新接口,供主题在通知时调用。
    • 具体观察者 (Concrete Observer): 实现观察者接口。它通常会维护一个对主题对象的引用,用于在接收到通知时,从主题中“拉取”所需的数据。它实现更新逻辑以响应主题的状态变化。
  2. 意图

    • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
    • 将主题与观察者解耦,使得它们可以独立地改变和复用。

三、适用场景剖析

观察者模式在以下场景中非常有效:

  1. 当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象有待改变时: 这有效地减少了对象间的耦合。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时: 将这二者封装在独立的对象中,使它们可以各自独立地改变和复用。
  3. 需要建立一种触发链机制时: 一个对象的改变会触发一系列操作,但这些操作并不需要集中式代码来处理。
  4. 跨系统或模块的事件通知机制: 例如,微服务架构中的领域事件发布、应用内的全局事件总线。

四、UML 类图解析(Mermaid)

以下UML类图清晰地展示了观察者模式的结构和角色间的关系:

observers
subject
«interface»
Subject
+attach(observer: Observer)
+detach(observer: Observer)
+notifyObservers()
ConcreteSubject
-state: Object
-observers: List<Observer>
+getState() : Object
+setState(state: Object)
«interface»
Observer
+update()
ConcreteObserver
-subject: Subject
+update()
  • Subject (主题接口): 声明了 attach, detach, notifyObservers 等方法,用于管理观察者列表。
  • ConcreteSubject (具体主题)
    • 维护一个观察者列表 (-observers: List~Observer~)。
    • 维护一个对自身有意义的状态 (-state: Object)。
    • 当状态改变时(通常在 setState 方法中),调用 notifyObservers() 方法。
    • notifyObservers() 方法会遍历 observers 集合,调用每个观察者的 update() 方法。
  • Observer (观察者接口): 声明一个 update() 方法。这是主题通知观察者的唯一方式。
  • ConcreteObserver (具体观察者)
    • 实现 update() 方法。当被主题通知时,此方法被调用。
    • 通常持有(或可以访问)一个对主题对象的引用 (-subject: Subject)。这样,在 update() 方法中,它就可以从主题“拉取” 它所需要的任何数据(例如调用 subject.getState()),而不是被动地接收主题“推送”过来的数据。
  • 调用流程
    1. ConcreteObserverConcreteSubject 注册自身 (attach)。
    2. ConcreteSubject 状态变更 (setState)。
    3. ConcreteSubject 调用 notifyObservers()
    4. notifyObservers() 遍历所有 Observer,调用每个 Observerupdate() 方法。
    5. update() 方法中,ConcreteObserverConcreteSubject 获取新状态并执行相应操作。

五、各种实现方式及其优缺点

观察者模式主要有两种实现变体:拉模型推模型

1. 拉模型 (Pull Model) - 更常用,更灵活

主题在通知观察者时,只发送一个简单的通知,而不包含改变的细节。观察者在接收到通知后,主动从主题对象中“拉取”所需的数据。

  • 实现: 正如上述UML所描述的,观察者的 update() 方法不带参数或只带一个主题引用。观察者需要自己调用主题的 getState() 等方法来获取详细信息。
  • 优点
    • 主题无需知道观察者的具体需求: 主题只是广播一个变化通知,观察者自己决定需要什么数据,这使得主题接口更简洁、稳定。
    • 可复用性高: 不同的观察者可以从同一个主题中获取自己关心的不同部分的数据。
  • 缺点
    • 效率可能稍低: 观察者可能需要多次调用主题的getter方法。
    • 观察者必须持有主题的引用

2. 推模型 (Push Model)

主题在通知观察者时,将改变的细节作为参数传递给观察者的 update() 方法。

  • 实现: 观察者的 update() 方法签名可能类似于 update(Event event, Object data)。主题将相关的数据封装后直接“推送”给观察者。
  • 优点
    • 高效: 一次调用传递所有数据。
    • 观察者可能无需持有主题引用
  • 缺点
    • 主题需要知道观察者的需求: 这可能导致主题接口变得复杂,或者传递的数据是某些观察者不需要的,造成浪费。
    • 降低了主题和观察者的复用性: 主题和观察者通过具体的数据类型耦合在一起。

最佳实践通常优先选择“拉模型”,因为它提供了更好的松耦合性。主题和观察者只依赖于抽象的接口,而不是具体的数据结构。

3. 实现方式的优缺点总结

  • 优点
    • 实现了主题与观察者的松耦合: 主题只知道观察者实现了某个接口,而不知道其具体类。
    • 支持广播通信: 主题一次通知,所有注册的观察者都会收到消息。
    • 符合开闭原则: 可以轻松地增加新的观察者,而无需修改主题的代码。
  • 缺点
    • 通知顺序的不确定性: 观察者被通知的顺序可能是任意的,不应依赖于特定的顺序。
    • 意外的更新: 如果观察者的更新操作非常耗时,或者嵌套调用了主题的方法,可能会导致循环调用或性能问题。

六、最佳实践

  1. 考虑线程安全: 在并发环境中,主题的 attach, detach, notifyObservers 方法以及状态变更方法都应该是线程安全的。通常可以使用同步机制(如 synchronized)或使用线程安全的集合(如 CopyOnWriteArrayList)来管理观察者列表。
  2. 避免在观察者中修改主题: 在观察者的 update() 方法中,应尽量避免调用会改变主题状态的方法,这可能导致复杂的递归调用链,难以理解和调试。
  3. 定义清晰的事件对象: 在推模型中,可以定义一个通用的事件对象(如 ValueChangedEvent)来封装变化信息,而不是传递一堆松散参数,这更易于扩展。
  4. 与中介者模式结合: 当观察者之间的关系非常复杂时,可以考虑引入一个中介者来管理它们之间的交互,避免观察者相互直接调用。
  5. 处理异步通知: 对于耗时较长的观察者处理逻辑,可以考虑使用异步方式通知观察者(例如,将通知任务提交到线程池),以避免阻塞主题线程。

七、在开发中的演变和应用

观察者模式的思想是现代事件驱动架构 (EDA)响应式编程 (Reactive Programming) 的核心:

  1. 消息中间件与事件总线: 在微服务架构中,消息队列(如Kafka, RabbitMQ)事件总线 是系统级别的观察者模式实现。服务(主题)发布事件到消息主题(Topic),其他服务(观察者)订阅这些主题并做出反应,实现了服务间的完全解耦。
  2. 响应式流 (Reactive Streams)Project ReactorRxJava 等响应式库将观察者模式推向极致。Flux/Observable 代表一个可观察的数据流(主题),开发者通过 subscribe(相当于 attach)并定义一系列操作(观察者链)来响应数据流中的元素(onNext)、错误(onError)和完成信号(onComplete)。
  3. 前端框架 (React, Vue, Angular): 这些框架的核心是数据驱动的视图。组件的状态(State/Data)是主题,视图(UI)是观察者。当状态发生变化时,框架会自动通知并更新视图(重新渲染)。
  4. JavaBean 绑定与属性变更监听: JavaFX 和许多UI框架使用属性绑定机制,其底层也是观察者模式。

八、真实开发案例(Java语言内部、知名开源框架、工具)

  1. Java Swing / AWT 事件监听机制

    • 这是观察者模式最经典的案例。JButton 等UI组件是具体主题
    • ActionListener观察者接口,定义了 actionPerformed(ActionEvent e) 方法。
    • 开发者实现的 ActionListener具体观察者
    • 通过 button.addActionListener(listener) (attach) 来注册观察者。
    • 当按钮被点击时,它会通知notifyObservers)所有注册的监听器,调用它们的 actionPerformed 方法。
  2. Java.util.Observable 和 Observer (已弃用)

    • Java早期在标准库中直接提供了对观察者模式的支持。
    • java.util.Observable 类作为主题。
    • java.util.Observer 接口作为观察者。
    • 由于其实现不够灵活(例如,Observable 是一个类,需要继承,而不是实现接口),在Java 9中被标记为弃用。但它是一个很好的教学例子。
  3. Spring Framework - ApplicationEvent 和 ApplicationListener

    • Spring的事件发布机制是观察者模式的工业级实现。
    • 主题ApplicationEventPublisher 接口(publishEvent(...) 方法)。
    • 观察者ApplicationListener 接口(onApplicationEvent(...) 方法)。
    • 具体事件: 自定义事件需继承 ApplicationEvent
    • 工作流程: 任何Bean都可以注入 ApplicationEventPublisher 来发布事件。任何Bean只要实现了 ApplicationListener 接口(或使用 @EventListener 注解),就会自动被注册为观察者,并在相应事件发布时被调用。
    • 这完美实现了Spring容器内Bean之间的解耦。
  4. Apache Kafka / RabbitMQ

    • 从架构层面看,消息队列是分布式的观察者模式实现。
    • 生产者 (Producer) 充当主题,发布消息到特定的Topic或Exchange。
    • 消费者 (Consumer) 充当观察者,订阅Topic或Queue,并在消息到达时得到通知和处理。
    • 这实现了服务级别的解耦和异步通信。
  5. ReactFX 和 JavaFX Property Change Listeners

    • JavaFX 的 Property 对象(如 SimpleStringProperty)允许添加变更监听器(InvalidationListenerChangeListener),当属性值改变时自动通知。这是观察者模式在UI数据绑定中的直接应用。

九、总结

方面总结
模式类型行为型设计模式
核心意图定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
关键角色主题(Subject), 具体主题(ConcreteSubject), 观察者(Observer), 具体观察者(ConcreteObserver)
核心机制1. 注册管理: 主题维护观察者列表,提供 attach/detach 方法。
2. 状态变更通知: 主题状态变化时调用 notifyObservers
3. 更新响应: 观察者实现 update 方法以响应通知。
实现变体拉模型 (灵活,松耦合),推模型 (高效,可能紧耦合)。
主要优点1. 实现解耦: 主题和观察者抽象耦合,可独立变化和复用。
2. 支持广播: 一对多通信非常高效。
3. 遵守开闭原则: 易于增加新观察者。
主要缺点1. 通知顺序不确定
2. 可能引起性能问题(观察者处理慢)或循环调用。
3. 调试可能较复杂
适用场景一个对象的变化需要影响其他对象,且不知道具体有多少对象需要被影响。
最佳实践优先选择拉模型;注意线程安全;避免在更新方法中修改主题;考虑异步通知。
现代应用事件驱动架构 (EDA)响应式编程 (Reactive Streams)消息中间件前端框架 (MVVM) 的基石。
真实案例Swing/AWT事件监听 (经典),Spring事件机制 (工业级),Kafka/RabbitMQ (分布式),JavaFX属性绑定

观察者模式是解耦艺术的典范,是构建灵活、可扩展、可维护系统的关键工具。它从GUI事件处理到分布式系统通信,无处不在,是每一位架构师和开发者必须深刻理解并熟练运用的核心模式。其思想催生了现代的事件驱动和响应式编程范式,掌握了它,就掌握了构建响应式、松耦合应用的一把钥匙。

posted @ 2025-08-30 00:20  NeoLshu  阅读(6)  评论(0)    收藏  举报  来源