news 2026/4/7 10:15:45

Java设计模式系列 - 观察者模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java设计模式系列 - 观察者模式

1. 观察者模式是什么

想象一下,你是一个杂志社(我们叫它“主题”或“发布者”)。你有一批忠实的订阅用户(我们叫他们“观察者”或“订阅者”)。

  • 你的工作(发布者):专心做好内容,比如出版新一期的《架构师周刊》。你不需要知道具体是谁订阅了,你只需要维护一个订阅名单
  • 订阅者的工作(观察者):他们向杂志社登记(订阅),说:“嘿,出新刊了记得通知我。”然后,他们该干嘛干嘛。
  • 关键动作(通知):当你这期杂志印刷好了(状态改变),你不会挨家挨户敲门。你会按照订阅名单,把新杂志统一寄送(通知)给所有订阅者。
  • 订阅者的反应(更新):订阅者收到杂志后,各自采取行动:老王可能泡杯茶开始读,小李可能转手送给朋友。

映射到代码里,就是这个意思: 一个核心对象(主题)的状态发生了变化,它自己不用操心要去通知谁、怎么通知。它只负责维护一个观察者列表,并在变化发生时,遍历这个列表,对每个观察者说一句:“喂,我变了!”(调用一个定义好的方法,比如update())。至于每个观察者听到这个消息后是去更新界面、刷新缓存、还是发个短信,主题完全不知道,也不关心。

这就是观察者模式的核心:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。它完美地将变化的发出者(主题)变化的响应者(观察者)解耦了。

2. 什么时候用

2.1 使用信号

当你看到以下场景时,观察者模式就该出场了:

  1. “牵一发而动全身”的时候:这是最经典的场景。比如,后台管理页面修改了某个核心的系统配置(主题状态改变)。这个改动需要立刻生效在:前端的多个页面展示、缓存的刷新、相关服务的配置热更新、甚至要记录一条审计日志。如果你用一堆if-else或者硬编码调用,代码会变成一坨乱麻,加一个影响点就得改核心代码。用观察者模式,配置服务就是主题,前端组件、缓存服务、审计服务都是观察者。配置一变,自动通知所有相关方,干净利落。
  2. “不知道有多少人关心”的时候:你的系统里有个事件,比如“用户成功支付”。未来可能有无数个模块关心这个事件:发优惠券、更新用户积分、通知物流系统、给运营发数据报表……你作为支付模块的开发者,根本不可能预知未来所有需求。用观察者模式,支付模块只管触发“支付成功”事件,谁关心谁自己来订阅。未来加十个新功能,支付模块的代码一行都不用动。
  3. “一个对象需要通知其他对象,但又不想跟它们绑死”的时候:这就是低耦合的诉求。比如你的数据模型(Model)变了,需要更新多个UI视图(View)。你肯定不希望数据模型里塞满了更新UI的代码。用观察者模式,UI视图作为观察者订阅数据模型的变化。模型变了,通知视图更新,模型完全不知道视图是怎么渲染的。

2.2 实际项目中的高频用例

  • 事件驱动架构:这是观察者模式的集大成者。消息队列(如Kafka、RabbitMQ)就是超级主题,微服务就是观察者。
  • GUI事件处理:几乎所有的UI框架(如Java Swing、Android)底层都是观察者模式。按钮点击、鼠标移动都是事件(主题),事件监听器就是观察者。
  • 应用内消息总线:比如Spring的ApplicationEvent机制,就是一个标准的观察者模式实现,用于解耦模块间的通信。
  • 实时数据同步:配置中心、分布式缓存的一致性通知。

3. 怎么实现

概念懂了,我们看看代码里长什么样。它通常包含四个角色:

  1. 主题(Subject)接口:定规矩。必须能添加(attach/addObserver)、删除(detach/removeObserver)观察者,并能通知(notifyObservers)所有观察者。
  2. 具体主题(ConcreteSubject):干实活。实现接口,内部维护一个观察者列表。它有自己的状态(比如int state),当状态改变时(比如setState()被调用),就遍历列表,调用每个观察者的更新方法。
  3. 观察者(Observer)接口:也是定规矩。通常就一个方法,比如update(),用来接收主题的通知。
  4. 具体观察者(ConcreteObserver):也是干实活。实现update()方法,定义自己接到通知后要做什么。它通常会持有主题的引用,以便在更新时获取主题的最新状态。

一段极简的代码骨架帮你理解(伪代码风格):

// 1. 主题接口 interface Subject { void addObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } // 2. 具体主题 class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private int importantData; // 核心状态 public void setImportantData(int newValue) { this.importantData = newValue; notifyObservers(); // 状态改变,立刻通知! } @Override public void notifyObservers() { for (Observer o : observers) { o.update(); // 调用每个观察者的更新方法 } } // ... 实现 add/remove 方法 } // 3. 观察者接口 interface Observer { void update(); } // 4. 具体观察者A class ConcreteObserverA implements Observer { private ConcreteSubject subject; // 持有主题引用,以便获取数据 @Override public void update() { // 主题通知我了,我要做我的事了 int data = subject.getImportantData(); System.out.println("Observer A: Data is now " + data); // 可能是更新UI,也可能是刷新缓存... } }

看到没?ConcreteSubjectsetImportantData里调用了notifyObservers,这就是“出版杂志”。ConcreteObserverAupdate方法就是“读杂志”。两者通过抽象的接口联系,没有直接依赖具体实现。

4. 优点与缺点

4.1 优点

  1. 解耦,解耦,还是解耦:这是最大的价值。主题和观察者之间只依赖于抽象接口,而不是具体类。主题不知道观察者是谁、有多少个、要干嘛。这符合“开放-封闭原则”,新增观察者无需修改主题代码。
  2. 支持广播通信:主题一次状态改变,能自动通知所有观察者,非常适合一对多的通信场景。
  3. 增强了系统的灵活性和可扩展性:可以随时动态地增加或删除观察者,系统行为可以很容易地改变。

4.2 缺点

  1. 通知链可能失控:如果观察者的更新方法里,又去修改了主题的状态,可能会触发新一轮的通知,形成循环调用,严重时导致系统崩溃。设计时要特别注意避免这种循环依赖。
  2. 性能隐患:如果观察者数量巨大(比如上万个),并且每个观察者的更新操作都很耗时,那么一次同步通知可能会阻塞主题很长时间,导致响应变慢。解决方案是考虑异步通知,比如将通知任务扔到线程池或消息队列里。
  3. 观察者只知道“变了”,不知道“怎么变”:在基础的实现里,主题的notifyObservers()方法只是简单调用o.update()。观察者需要自己通过持有的主题引用来拉取(pull)最新数据。这有时不够高效。改进方法是采用推模型,在通知时把变化的数据(或事件对象)作为参数传递给update(Event event)方法。
  4. 内存泄漏风险:如果观察者被注册后没有被正确移除,而观察者对象又因为被主题引用而无法被垃圾回收,就会导致内存泄漏。尤其在Web应用中,用户会话相关的观察者需要特别注意在会话结束时注销。

5. 实战建议

  1. 优先使用现成框架:别自己从头撸轮子。在Java生态里,java.util.Observablejava.util.Observer是内置实现,但比较简陋且类而不是接口,不推荐在新项目中使用。Spring的事件机制(ApplicationEventPublisher@EventListener) 是生产级别的首选,它支持同步/异步、事件继承、条件过滤等高级特性。
  2. 考虑事件对象(Event Object):不要只传一个空的通知。定义一个事件类(如OrderPaidEvent),里面包含事件相关的所有数据(订单ID、金额、时间等)。在通知时把这个事件对象传过去。这样观察者无需再回查主题,效率更高,信息也更完整。
  3. 小心处理异常:在主题的notifyObservers方法里,如果直接循环调用观察者,一个观察者抛出异常会导致后续的观察者收不到通知。通常需要做异常捕获和日志记录,确保一个观察者的失败不影响整体。
  4. 明确生命周期:谁负责注册观察者?谁负责销毁?在Web应用中,通常在Bean初始化时注册,在上下文销毁时移除。清晰的生命周期管理能避免一堆诡异的问题。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/6 18:39:23

产品改进建议收集:来自一线的声音

Anything-LLM 核心架构解析&#xff1a;从个人助手到企业级知识中枢的演进之路 在信息爆炸的时代&#xff0c;我们每天都被海量文档包围——PDF 报告、Word 手册、Excel 表格、PPT 汇报……这些非结构化数据如同散落的拼图&#xff0c;难以快速整合成可用的知识。传统的搜索方式…

作者头像 李华
网站建设 2026/3/27 19:41:52

7、管理用户账户:Windows 2000 中的用户配置文件、主文件夹与组策略

管理用户账户:Windows 2000 中的用户配置文件、主文件夹与组策略 在 Windows 2000 系统中,管理用户账户是一项重要的任务,它涉及到用户配置文件、主文件夹和组策略等方面。这些功能为管理员提供了强大的工具,有助于提高用户生产力和降低管理成本。 1. 用户配置文件概述 …

作者头像 李华
网站建设 2026/3/30 9:29:46

7、打造魅力应用:搜索与筛选功能全解析

打造魅力应用:搜索与筛选功能全解析 在开发应用时,搜索和筛选功能是提升用户体验的关键部分。本文将详细介绍如何在应用中实现搜索筛选功能,以及如何提供搜索建议,包括从本地列表、已知文件夹和在线源获取建议。 实现筛选功能 当搜索功能实现后,为用户提供筛选功能是很…

作者头像 李华
网站建设 2026/4/4 2:40:49

10、Windows 开发:实时磁贴、徽章与通知的使用

Windows 开发:实时磁贴、徽章与通知的使用 在 Windows 开发中,实时磁贴、徽章和通知是提升应用用户体验的重要元素。下面将详细介绍它们的使用方法和相关代码实现。 为辅助磁贴添加导航功能 在 Windows RT 开发里,要让辅助磁贴导航到特定页面,与 Windows Phone 开发有所不…

作者头像 李华
网站建设 2026/4/4 7:42:25

端到端语音大模型高质量数据集典型案例

一、背景 当前语音大模型在落地应用中面临多语言数据稀缺、方言覆盖不足、场景适配能力弱等挑战。标贝科技采用"多源采集生成增强智能管线"架构体系&#xff0c;构建了总时长超过130万小时的高质量端到端语音大模型数据集&#xff0c;涵盖全球30余种语言及方言&#…

作者头像 李华
网站建设 2026/4/6 1:42:19

合规护航发展:智慧管理时代,每家企业都需筑牢的“生命线”

近日&#xff0c;国家市场监督管理总局联合国务院国资委&#xff0c;面向中央企业举办了以“加强反垄断合规&#xff0c;服务高质量发展”为主题的反垄断合规讲堂。讲堂明确指出&#xff0c;“要落实企业主体责任&#xff0c;坚持依法合规经营”&#xff0c;并着力构建与一流企…

作者头像 李华