观察者模式:原理、实现与应用场景解析
1. 观察者模式概述观察者模式Observer Pattern是23种经典设计模式中行为型模式的一种它定义了对象之间的一对多依赖关系当一个对象的状态发生改变时所有依赖于它的对象都会得到通知并自动更新。这种模式在事件驱动系统、消息队列、GUI组件交互等场景中应用广泛。我在实际项目中最常遇到观察者模式的场景是用户通知系统。比如当某个商品价格变动时所有关注该商品的用户都需要收到通知。如果不用观察者模式我们可能需要写大量硬编码的条件判断和手动调用而观察者模式让这种一对多的通知变得优雅而高效。2. 观察者模式的核心结构2.1 角色划分观察者模式主要包含四个核心角色Subject主题被观察的对象维护一个观察者列表提供添加、删除观察者的方法以及通知观察者的机制。在Java中通常是一个接口或抽象类。ConcreteSubject具体主题Subject的具体实现维护自身状态当状态改变时通知所有注册的观察者。Observer观察者定义更新接口在接到主题通知时更新自己。通常是一个接口。ConcreteObserver具体观察者Observer的具体实现维护一个指向ConcreteSubject的引用实现更新逻辑。2.2 UML类图解析[Subject] - [ConcreteSubject] [Subject] - [Observer] [Observer] - [ConcreteObserver]这种结构体现了松耦合的设计思想 - 主题只知道观察者实现了某个接口但不知道具体观察者是谁同样观察者也只知道主题的接口。这种松耦合让系统更灵活更容易扩展新的观察者或主题。3. 观察者模式的实现方式3.1 推模型 vs 拉模型观察者模式有两种主要的实现方式推模型Push Model主题将详细的变更信息推送给观察者。这种方式效率高但不够灵活观察者可能收到不需要的数据。拉模型Pull Model主题只通知观察者状态已改变观察者根据需要从主题拉取数据。这种方式更灵活但可能增加系统开销。在实际项目中我通常根据以下原则选择如果观察者总是需要全部数据 → 推模型如果观察者需要选择性获取数据 → 拉模型如果网络带宽是瓶颈 → 拉模型3.2 Java内置观察者实现Java在java.util包中提供了Observable类和Observer接口这是观察者模式的经典实现。不过从Java 9开始它被标记为Deprecated主要是因为不是接口而是类限制了复用性方法是同步的可能影响性能没有提供丰富的功能如过滤特定事件尽管如此对于简单的场景它仍然是一个不错的选择。下面是一个使用Java内置观察者的示例// 被观察者 class WeatherData extends Observable { private float temperature; public void setTemperature(float temp) { this.temperature temp; setChanged(); // 标记状态已改变 notifyObservers(temp); // 通知观察者 } } // 观察者 class Display implements Observer { Override public void update(Observable o, Object arg) { System.out.println(温度更新为: arg); } } // 使用 WeatherData weatherData new WeatherData(); Display display new Display(); weatherData.addObserver(display); weatherData.setTemperature(25.5f);3.3 自定义观察者实现对于更复杂的场景我通常会自定义观察者模式。以下是一个更灵活的实现// 主题接口 interface SubjectT { void registerObserver(ObserverT o); void removeObserver(ObserverT o); void notifyObservers(T data); } // 观察者接口 interface ObserverT { void update(T data); } // 具体主题 class NewsPublisher implements SubjectString { private ListObserverString observers new ArrayList(); Override public void registerObserver(ObserverString o) { observers.add(o); } Override public void notifyObservers(String news) { for (ObserverString o : observers) { o.update(news); } } public void publishNews(String news) { System.out.println(发布新闻: news); notifyObservers(news); } } // 具体观察者 class NewsSubscriber implements ObserverString { private String name; public NewsSubscriber(String name) { this.name name; } Override public void update(String news) { System.out.println(name 收到新闻: news); } }4. 观察者模式的高级应用4.1 事件总线实现在大型系统中直接使用观察者模式可能导致观察者列表过长影响性能。这时可以使用事件总线Event Bus作为中间层class EventBus { private MapClass?, ListConsumer? handlers new ConcurrentHashMap(); public T void subscribe(ClassT eventType, ConsumerT handler) { handlers.computeIfAbsent(eventType, k - new ArrayList()).add(handler); } SuppressWarnings(unchecked) public T void publish(T event) { ListConsumer? consumers handlers.get(event.getClass()); if (consumers ! null) { consumers.forEach(c - ((ConsumerT)c).accept(event)); } } } // 使用示例 EventBus bus new EventBus(); bus.subscribe(String.class, msg - System.out.println(处理字符串: msg)); bus.publish(测试消息);4.2 响应式编程中的观察者现代响应式编程库如RxJava、Reactor都基于观察者模式的变体。以RxJava为例ObservableString observable Observable.create(emitter - { emitter.onNext(消息1); emitter.onNext(消息2); emitter.onComplete(); }); ObserverString observer new ObserverString() { Override public void onNext(String s) { System.out.println(收到: s); } Override public void onComplete() { System.out.println(完成); } }; observable.subscribe(observer);响应式编程扩展了传统观察者模式增加了背压处理、错误处理、线程调度等高级特性。5. 观察者模式的优缺点与适用场景5.1 优点分析松耦合主题和观察者之间抽象耦合彼此不知道对方的具体实现。动态关系可以在运行时动态添加或删除观察者。广播通信一个主题变更可以通知多个观察者无需显式指定。符合开闭原则新增观察者无需修改主题代码。5.2 缺点与注意事项通知顺序不确定观察者被通知的顺序通常是不确定的不应依赖特定顺序。性能问题如果观察者过多或处理耗时可能导致系统性能下降。内存泄漏观察者忘记注销可能导致内存泄漏特别是在长生命周期的主题中。循环依赖观察者之间相互通知可能导致无限循环。5.3 最佳实践场景根据我的经验观察者模式特别适合以下场景事件驱动系统如GUI事件处理、异步消息处理。监控系统如系统状态监控、性能指标收集。发布-订阅模型如新闻推送、社交媒体更新。分布式系统如服务状态变更通知。6. 观察者模式与其他模式的关系6.1 与发布-订阅模式的区别很多人容易混淆观察者模式和发布-订阅模式它们确实相似但有重要区别耦合度观察者模式中主题知道观察者发布-订阅模式中发布者和订阅者通过中间件通信彼此不知道对方。同步性观察者模式通常是同步的发布-订阅模式通常是异步的。实现复杂度发布-订阅模式通常需要消息代理实现更复杂。6.2 与中介者模式的对比中介者模式也处理对象间通信但目的不同观察者模式一个对象通知多个依赖对象中介者模式集中管理多个对象间的复杂交互7. 实际项目中的经验分享7.1 性能优化技巧在大规模使用观察者模式时我总结了一些性能优化经验批量通知对于高频变更的主题可以实现批量通知机制积累一定量变更后统一通知。异步通知使用线程池异步通知观察者避免阻塞主题线程。观察者分级根据观察者的重要性分级关键观察者同步通知非关键观察者异步通知。懒加载观察者对于资源密集型观察者可以实现懒加载机制。7.2 常见问题排查通知丢失确保在主题状态变更后调用通知方法最好在setter方法中自动触发。内存泄漏使用WeakReference存储观察者引用或确保在适当时候注销观察者。线程安全问题如果主题和观察者可能在不同线程访问需要同步观察者列表操作。过度通知实现状态变更检测只有真正改变时才通知。7.3 测试策略测试观察者模式时我通常采用以下策略模拟观察者使用Mock对象验证是否收到正确通知。通知顺序测试如果业务依赖通知顺序需要专门测试。并发测试模拟多线程环境下的通知行为。性能测试测量大量观察者时的通知延迟。8. 现代Java中的观察者模式8.1 Java 9的替代方案由于java.util.Observable被废弃现代Java项目可以考虑PropertyChangeListenerJavaBeans的标准属性变更通知机制。Flow APIJava 9引入的响应式流API。第三方库如Guava的EventBus、Spring的事件机制。8.2 Spring框架中的观察者Spring框架内置了强大的观察者模式实现// 定义事件 class MyEvent extends ApplicationEvent { public MyEvent(Object source) { super(source); } } // 发布事件 Component class EventPublisher { Autowired private ApplicationEventPublisher publisher; public void publish() { publisher.publishEvent(new MyEvent(this)); } } // 监听事件 Component class EventListener { EventListener public void handle(MyEvent event) { System.out.println(收到事件: event); } }Spring的实现支持异步事件、条件监听、事务绑定等高级特性。9. 观察者模式的演进与未来随着响应式编程的流行观察者模式也在不断发展响应式流提供背压支持防止生产者压垮消费者。函数式风格使用Lambda表达式简化观察者实现。跨语言支持如Web的WebSocket、gRPC的流式调用。分布式观察者如使用消息队列实现跨服务的观察者模式。观察者模式作为解耦组件的重要工具在未来系统设计中仍将扮演关键角色。掌握其核心思想并能根据具体场景灵活变通是每个开发者必备的技能。

相关新闻

企业AI Agent定制:下拉框之外的值为何被填了出来?

企业AI Agent定制:下拉框之外的值为何被填了出来?

一家做设备维保的企业在月末汇总工单时,发现统计表里冒出一批对不上的记录。助手受理客户报修后,会把处理结果回填到工单系统,可它填进去的已上门处理完成、客户暂时不需要这类字样,并不在系统预设的取值范围内,汇总脚…

2026/10/14 11:38:48 阅读更多 →
MCP安全网关实战:用Python检测工具投毒与Rug Pull,筑牢AI Agent防线

MCP安全网关实战:用Python检测工具投毒与Rug Pull,筑牢AI Agent防线

前阵子和几个做 AI 应用的朋友聊天,大家不约而同提到了同一个焦虑:Agent 越来越能干了,但谁也不敢保证它调用的那些工具是干净的。这个焦虑不是杞人忧天——当 MCP(Model Context Protocol)把大模型和外部工具连接起来…

2026/10/14 11:38:48 阅读更多 →
从模糊标题到生产系统:响应式编程与实时流处理实战指南

从模糊标题到生产系统:响应式编程与实时流处理实战指南

1. 从“rea”这个标题说起:一个被低估的缩写背后藏着什么第一次看到“rea”这个标题的时候,我脑子里蹦出来的第一反应是——这大概率又是一个被缩写玩坏了的项目名。做技术的人都有个通病,喜欢把什么都缩成三个字母,仿佛字数越少越…

2026/10/14 11:37:47 阅读更多 →

最新新闻

SolidWorks模板制作全指南:从零件到工程图的属性联动

SolidWorks模板制作全指南:从零件到工程图的属性联动

简介:使用SolidWorks的设计与工程人员若希望统一零件、装配体和工程图的格式,并符合GB标准,这份PDF提供了从模板原理到属性链接的完整知识点。文档先梳理了零件模板( .prtdot)、装配模板( .asmdot)、工程图模板(*.drwdot)的扩展名区别、默认…

2026/10/14 12:39:46 阅读更多 →
项目管理中的范围管理:从WBS到变更控制的完整实践指南

项目管理中的范围管理:从WBS到变更控制的完整实践指南

1. 从两个经典翻车场景,看懂范围管理到底管什么先讲两个我亲眼见过的项目翻车现场,都是典型的范围管理失控。第一个场景。某公司要上一个内部管理系统,业务部门提需求时说得很简单:“我们就要一个能看数据的后台就行。”开发团队听…

2026/10/14 12:39:46 阅读更多 →
PHFTP部署指南:用PHP打造浏览器端的FTP客户端全攻略

PHFTP部署指南:用PHP打造浏览器端的FTP客户端全攻略

简介:PHFTP(PHP HTTP FTP)是一款基于PHP的开源Web FTP客户端,面向需要在浏览器端直接管理远程FTP服务器的开发者、运维人员及建站用户。它摆脱了桌面FTP工具的安装依赖,即便在防火墙或代理限制下,也能通过网…

2026/10/14 12:39:46 阅读更多 →
Kepserver主备冗余、MySQL双向读写与后台服务配置全指南

Kepserver主备冗余、MySQL双向读写与后台服务配置全指南

简介:在工业现场数据对接项目中,KEPServer与MySQL、西门子PLC之间的数据联通是常见又容易出错的环境,这份doc文档正好围绕这些场景提供了系统化的配置参考。文档重点覆盖了主备切换、MySQL与西门子PLC双向读写、后台服务设置以及地址值填写四…

2026/10/14 12:39:46 阅读更多 →
python的机器人基础原理工业场景模拟:搬运工作站,给定关节角度,角速度,角加速度,调用工具箱直接求解全套关节力矩。

python的机器人基础原理工业场景模拟:搬运工作站,给定关节角度,角速度,角加速度,调用工具箱直接求解全套关节力矩。

《Python机器人基础原理》第十八篇:搬运工作站——给定关节状态,基于动力学术解全套关节力矩1. 实际工业应用场景周五下午,某汽车零部件厂的发动机缸体搬运工作站。一台3-DOF搬运机械臂(基座偏航肘部俯仰垂直升降)负责…

2026/10/14 12:39:46 阅读更多 →
AI学术速递系统搭建:从信息过载到精准筛选的工程实践

AI学术速递系统搭建:从信息过载到精准筛选的工程实践

1. 从“学术速递”这个动作说起:为什么信息筛选比信息获取更难每天早上刷一遍学术平台的新论文列表,这件事我坚持了快三年。最开始是手动刷,后来用订阅工具,再后来自己写脚本抓取。折腾一圈下来发现,真正的痛点从来不是…

2026/10/14 12:38:45 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →