news 2026/7/12 2:07:35

Java 23 种设计模式:从踩坑到精通 | 观察者模式 —— 发布-订阅,你每天都在用的模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 23 种设计模式:从踩坑到精通 | 观察者模式 —— 发布-订阅,你每天都在用的模式

Java 23 种设计模式:从踩坑到精通 | 观察者模式 —— 发布-订阅,你每天都在用的模式

摘要:当对象间存在一对多依赖关系,一个对象的状态变化需要自动通知其他对象时,将通知逻辑直接写死在状态变化的代码中会导致高度耦合、难以扩展。观察者模式通过定义一种订阅机制,让“主题”对象管理它的“观察者”列表,状态变化时自动广播通知,从而将通知逻辑与业务逻辑解耦。本文从气象站数据推送的场景出发,完整讲解观察者模式的原理、UML、推模型与拉模型两种实现、与发布-订阅模式的区别,并结合 JavaEventListener、Spring 事件、GuavaEventBus、RxJava 等框架应用,帮你彻底掌握“状态变化即通知”的设计精髓。

🗺️本文阅读地图(3 分钟速览)

  • 为什么气象站不能直接调用所有面板的刷新方法?
  • 推模型 vs 拉模型:通知数据的两种策略
  • 手写气象站通知系统(拉模型,推荐)
  • Spring 事件:观察者模式的开箱即用
  • JDKObserver/ GuavaEventBus/ RxJava 如何体现
  • 面试必问:观察者 vs 发布-订阅,有什么区别?

📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:备忘录模式 |当前:观察者模式| 下一篇:状态模式
🔗 返回系列总目录


1. 从“气象站数据更新”的广播需求说起

假设你在开发一个气象站监控系统。气象站采集温度、湿度数据后,需要同步更新多个显示面板:实时温度看板、湿度看板、趋势图、日志记录器……如果直接在气象站类中调用每个面板的更新方法:

classWeatherStation{privateDisplayPaneldisplay;privateLoggerlogger;privateTrendChartchart;voidupdateData(floattemp,floathumidity){this.temp=temp;this.humidity=humidity;display.refresh(temp,humidity);logger.log(temp,humidity);chart.update(temp,humidity);}}

这种写法的问题显而易见:

  • 气象站与每个输出设备紧耦合,新增一个设备(如手机推送)就要改气象站代码;
  • 设备增减、替换都需要修改气象站类,违反开闭原则;
  • 单元测试时气象站必须带着所有设备一起测,无法独立测试。

观察者模式(Observer Pattern)正是为此而生:它定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

1.1 你的场景该不该用观察者?

判断标准是 → 用观察者否 → 用其他方式
一个对象的状态变化需要通知多个其他对象
被通知的对象列表可能动态增减
希望通知逻辑与业务逻辑解耦
只有一对一通知,或通知列表固定直接调用即可

2. 模式定义与 UML 结构

观察者模式定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。它属于行为型设计模式

图文解析(配合上述 UML 图)

观察者模式的核心角色:

  • 抽象主题(Subject:定义添加、删除、通知观察者的接口,持有观察者集合。它知道有哪些观察者,但不关心它们具体做什么。
  • 具体主题(ConcreteSubject:实现主题接口,当内部状态变化时主动调用notifyObservers()通知所有观察者。
  • 抽象观察者(Observer:定义一个更新接口,当主题变化时被调用。
  • 具体观察者(ConcreteObserverA/ConcreteObserverB:实现更新接口,在update()中获取主题的最新状态并执行自己的逻辑。

核心机制:主题只负责“我有新数据了,你们自己来拿”,观察者各自决定要取哪些数据、做什么处理。这就是“拉模型”的核心思想。


3. 推模型 vs 拉模型

观察者模式有两种通知风格:

对比维度推模型拉模型
通知方式主题将所有数据推送给观察者主题只通知“我变了”,观察者主动拉取所需数据
灵活性较低,观察者被动接受全部数据较高,观察者按需获取
耦合度主题需要知道观察者需要什么数据观察者需要持有主题引用并知道有哪些数据
适用场景数据量小、所有观察者需求一致数据量大、观察者需求各异

实际开发中拉模型更常用,因为它给了观察者更大的自由度。下面分别提供两种模型的实现。


4. 代码实现:气象站(拉模型,推荐)

4.1 抽象观察者

publicinterfaceObserver{voidupdate();// 主题只通知“我变了”,不带数据}

💬白话:观察者只需要知道“变了”,至于变什么、取什么数据,自己决定。

4.2 抽象主题

publicinterfaceSubject{voidattach(Observerobserver);voiddetach(Observerobserver);voidnotifyObservers();}

💬白话:主题提供“关注”、“取关”和“喊一声”三个能力。

4.3 具体主题:气象站

publicclassWeatherStationimplementsSubject{privateList<Observer>observers=newArrayList<>();privatefloattemperature;privatefloathumidity;@Overridepublicvoidattach(Observerobserver){observers.add(observer);}@Overridepublicvoiddetach(Observerobserver){observers.remove(observer);}@OverridepublicvoidnotifyObservers(){for(Observerobserver:observers){observer.update();// 只通知,不给数据}}// 状态变化时自动通知publicvoidsetData(floattemperature,floathumidity){this.temperature=temperature;this.humidity=humidity;System.out.println("【气象站】数据更新:温度="+temperature+"°C, 湿度="+humidity+"%");notifyObservers();}// 提供 getter,供观察者拉取所需数据publicfloatgetTemperature(){returntemperature;}publicfloatgetHumidity(){returnhumidity;}}

💬白话:气象站每次数据更新后自动喊一声“我变了”,至于观察者关心温度还是湿度,自己去取。

4.4 具体观察者:温度看板(只关心温度)

publicclassTemperatureDisplayimplementsObserver{privateWeatherStationstation;publicTemperatureDisplay(WeatherStationstation){this.station=station;station.attach(this);// 构造时自动订阅}@Overridepublicvoidupdate(){floattemp=station.getTemperature();System.out.println("【温度看板】当前温度:"+temp+"°C");}}

💬白话:温度看板只拉温度,湿度看板只拉湿度,各自按需取数据。

4.5 具体观察者:日志记录器(关心全部数据)

publicclassDataLoggerimplementsObserver{privateWeatherStationstation;publicDataLogger(WeatherStationstation){this.station=station;station.attach(this);}@Overridepublicvoidupdate(){floattemp=station.getTemperature();floathumidity=station.getHumidity();System.out.println("【日志】记录数据 → 温度:"+temp+"°C, 湿度:"+humidity+"%");}}

4.6 客户端

WeatherStationstation=newWeatherStation();TemperatureDisplaytempDisplay=newTemperatureDisplay(station);DataLoggerlogger=newDataLogger(station);station.setData(26.5f,65.0f);station.setData(27.0f,60.0f);

输出:

【气象站】数据更新:温度=26.5°C, 湿度=65.0% 【温度看板】当前温度:26.5°C 【日志】记录数据 → 温度:26.5°C, 湿度:65.0% 【气象站】数据更新:温度=27.0°C, 湿度=60.0% 【温度看板】当前温度:27.0°C 【日志】记录数据 → 温度:27.0°C, 湿度:60.0%

新增一个湿度看板,只需新建一个HumidityDisplay类并在构造函数中attach气象站代码零修改


5. 代码实现:推模型(完整示例)

推模型在update()时直接把数据推送给观察者。

// 抽象观察者(带参数)publicinterfacePushObserver{voidupdate(floattemperature,floathumidity);}// 具体观察者publicclassTempDisplayimplementsPushObserver{@Overridepublicvoidupdate(floattemperature,floathumidity){System.out.println("【推模型-温度看板】温度:"+temperature+"°C");}}// 主题publicclassPushWeatherStation{privateList<PushObserver>observers=newArrayList<>();publicvoidattach(PushObservero){observers.add(o);}publicvoiddetach(PushObservero){observers.remove(o);}publicvoidnotifyObservers(floattemp,floathumidity){for(PushObservero:observers){o.update(temp,humidity);// 推送全部数据}}publicvoidsetData(floattemp,floathumidity){notifyObservers(temp,humidity);}}

💬白话:推模型简单直接,但所有观察者都要接收全部数据。如果新增一个只需要湿度的观察者,它也得接收温度数据。


6. 优缺点一览

优点缺点
解耦:主题与观察者松耦合,各自可独立变化通知顺序不可控:观察者的通知顺序取决于集合的实现
广播通知:一对多通知自动完成性能开销:观察者多时,每次状态变化都要遍历通知
开闭原则:新增观察者无需修改主题代码可能导致循环依赖:观察者又修改了主题状态,触发新一轮通知
动态订阅:运行时自由增加/移除观察者难以调试:通知链较长时,排查问题需要追踪多个观察者的调用

7. 观察者模式 vs 发布-订阅模式 vs 中介者模式

对比维度观察者模式发布-订阅模式中介者模式
耦合度主题知道观察者列表(松耦合)发布者和订阅者完全解耦,通过 Broker 中转同事对象通过中介者协调
通信方式直接调用update()通过消息队列/事件总线通过notify()中转
适用层级进程内(单应用)跨进程、分布式进程内,多个对象交互
典型应用Spring 事件、Swing 监听器RabbitMQ、Kafka、Redis Pub/Sub聊天室、DispatcherServlet

💡简单记忆:观察者是“直接订阅,主题知情”;发布-订阅是“隔了一层 Broker,发布者不知道谁订阅”;中介者是“中间人协调双向交互”。


8. 框架与实践中的应用

8.1 JDK:java.util.Observer/Observable(已过时)

JDK 1.0 就提供了内置的观察者模式支持。虽然从 Java 9 开始被标记为过时,但它仍是理解观察者模式的绝佳起点——Observable是主题,Observer是观察者。

8.2 Spring 事件机制:ApplicationEvent+@EventListener

Spring 的事件驱动模型就是观察者模式的经典实现。ApplicationContext充当主题,@EventListener标注的 Bean 方法充当观察者。

@ComponentpublicclassOrderEventListener{@EventListenerpublicvoidhandleOrderCreated(OrderCreatedEventevent){// 发送通知、记录日志...}}

8.3 Google GuavaEventBus

Guava 的EventBus提供了进程内的事件总线,支持同步和异步订阅,使用@Subscribe注解即可。

8.4 RxJava / Project Reactor

响应式编程框架将观察者模式发挥到极致,Observable(被观察者)和Observer(观察者)的命名本身就致敬了经典模式,并加入了背压、操作符等现代特性。


9. 面试必问 + 面试官追问连环炮

基础必问

  • 观察者模式的核心角色?Subject(主题)、Observer(观察者)、ConcreteSubjectConcreteObserver
  • 推模型和拉模型的区别?→ 推模型主题推送全部数据,拉模型只通知变化,观察者主动拉取所需数据。
  • Spring 的事件机制用了什么模式?→ 观察者模式,ApplicationContext是主题,@EventListener是观察者。

面试官追问

  • “观察者模式和发布-订阅模式有什么区别?”
    👉 观察者模式中主题直接维护观察者列表并调用其方法;发布-订阅模式中两者通过 Broker 解耦,通常用于分布式系统。
  • “如何避免观察者之间的循环依赖?”
    👉 在update()方法中避免直接修改主题状态。如果必须修改,使用标志位或异步通知打破循环。
  • “Java 9 为什么废弃Observer/Observable?”
    👉 设计简陋,不支持泛型,Observable是类而非接口,灵活性差。建议使用PropertyChangeListener或 Spring 事件替代。

🎉恭喜:如果你能立刻说出 Spring 事件是观察者模式,并清楚推模型与拉模型的区别,你已经掌握了行为型模式中应用最广泛的“广播通知”设计。


10. 六大设计原则在观察者模式中的体现

设计原则在观察者模式中的体现
单一职责原则(SRP)主题负责状态管理,观察者负责响应变化
开闭原则(OCP)新增观察者无需修改主题代码
里氏替换原则(LSP)任何Observer子类都可替换抽象观察者
依赖倒置原则(DIP)主题依赖抽象Observer接口,不依赖具体观察者
接口隔离原则(ISP)Observer接口只定义update(),方法精简
迪米特法则(LoD)主题只知道Observer接口,不知道具体观察者的内部实现

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

  • 开篇:系列介绍与目录
  • 上一篇:备忘录模式 —— 快照与撤销,给对象装一个“后悔药”
  • 当前:观察者模式 —— 发布-订阅,你每天都在用的模式(你在这里)
  • 下一篇:状态模式 —— if-else 满天飞?让状态自己决定行为 🚧 即将发布
  • 创建型模式汇总:单例、工厂、建造者、原型
  • 结构型模式汇总:适配器、装饰器、代理……
  • 行为型模式汇总:观察者、策略、模板方法……

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀下一篇:状态模式:if-else 满天飞?让状态自己决定行为!🚧 即将发布,敬请关注!

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/12 2:07:35

靠谱的成人高考服务商

最近后台有不少粉丝私信我&#xff1a;“想找个机构报名成人高考&#xff0c;但网上广告铺天盖地&#xff0c;到底哪家靠谱&#xff1f;”作为在大连生活了十几年&#xff0c;身边不少朋友都通过学历提升改变了命运的职场人&#xff0c;我太懂这种焦虑了。辽宁成人学历市场&…

作者头像 李华
网站建设 2026/7/12 2:07:21

深入解析Beyond Compare 5授权机制:从RSA原理到实战激活

1. 项目概述&#xff1a;为什么我们需要深入理解Beyond Compare的授权机制作为一名在软件开发领域摸爬滚打了十多年的老程序员&#xff0c;我几乎每天都要和代码、配置文件、日志文件打交道。在这些日常工作中&#xff0c;文件对比工具就像我的左右手&#xff0c;而Beyond Comp…

作者头像 李华
网站建设 2026/7/12 2:07:06

如何彻底告别网盘下载限速?LinkSwift网盘直链下载助手完整指南

如何彻底告别网盘下载限速&#xff1f;LinkSwift网盘直链下载助手完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘…

作者头像 李华
网站建设 2026/7/12 2:06:38

Mac Mouse Fix:让你的普通鼠标在macOS上获得专业级体验

Mac Mouse Fix&#xff1a;让你的普通鼠标在macOS上获得专业级体验 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix 还在为macOS上第三方鼠标的糟…

作者头像 李华
网站建设 2026/7/12 2:06:17

Android Studio CPU Profiler 实战:3种抓取方式定位启动耗时 200ms 瓶颈

Android Studio CPU Profiler 实战&#xff1a;3种抓取方式定位启动耗时 200ms 瓶颈当用户点击应用图标时&#xff0c;那短暂的等待时间往往决定了他们对产品的第一印象。200毫秒的延迟看似微不足道&#xff0c;但在高性能移动应用的竞技场上&#xff0c;这足以让用户感知到&qu…

作者头像 李华
网站建设 2026/7/12 2:05:41

Masscan 1.3.2 UDP端口扫描实战:10万包/秒速率下识别DNS/NTP服务

Masscan 1.3.2 UDP端口扫描实战&#xff1a;10万包/秒速率下的DNS/NTP服务识别在网络安全评估和渗透测试中&#xff0c;UDP端口扫描往往是被忽视但至关重要的环节。与TCP扫描不同&#xff0c;UDP协议的无连接特性使得其扫描面临独特挑战。本文将深入探讨如何利用Masscan 1.3.2在…

作者头像 李华