当前位置: 首页 > news >正文

别再 if-else 疯狂嵌套了!观察者模式让你代码优雅到离谱

一、问题背景:为什么写这篇?

最近在做一个小需求——订单状态变更后,要同时触发库存扣减、短信通知、积分变更、日志记录……

我第一反应是什么?简单啊!

if(order.getStatus()==PAID){inventoryService.deduct(order);smsService.sendNotice(order);pointsService.addPoints(order);logService.saveLog(order);}
if(order.getStatus()==PAID){inventoryService.deduct(order);smsService.sendNotice(order);pointsService.addPoints(order);logService.saveLog(order);couponService.issueCoupon(order);// 新加的statsService.record(order);// 新加的pushService.push(order);// 新加的// 未来还有 N 个服务等着加...}

这就是典型的"牵一发动全身"——每加一个功能,都得改订单核心逻辑,改完还得重新测试整个流程。

程序跑起来了,但维护的人头发少了 😅

后来我想起来了——这不就是观察者模式的经典场景吗?

谁该干活,谁自己订阅就行了,别都挤到订单里来!


二、观察者模式是什么?

用一句话说:

一个对象状态变了,所有依赖它的对象自动收到通知并更新。

生活中的例子:

  • 微信群:有人发消息,所有群成员都能收到
  • B站关注:UP主发新视频,粉丝收到推送
  • 红绿灯:灯变了,所有车和行人同时响应

核心思想就四个字:发布-订阅


三、UML 结构(先看图再写代码)

┌─────────────┐ ┌─────────────┐ │ Subject │ │ Observer │ │ (被观察者) │<>─────│ (观察者接口) │ │─────────────│ │─────────────│ │+attach() │ │+update() │ │+detach() │ └─────────────┘ │+notify() │ △ └──────┬──────┘ │ △ │ │ ┌───────┴───────┐ │ │ │ ┌──────┴──────┐ ┌────┴────┐ ┌──────┴────┐ │ConcreteSubj │ │ObserverA│ │ObserverB │ │(具体被观察者) │ │(具体观察者)│ │(具体观察者) │ └─────────────┘ └─────────┘ └───────────┘

两个角色

  1. Subject(被观察者):维护观察者列表,状态变化时通知所有人
  2. Observer(观察者):定义更新接口,收到通知后做自己该做的事

关键原则

被观察者不关心观察者具体是谁、具体干什么,只负责"喊一嗓子"。

这就是松耦合的精髓。


四、手写实现:从 0 到 1 撸一个观察者模式

1. 定义观察者接口

/** * 观察者接口 - 所有订阅者必须实现 */publicinterfaceObserver{/** * 收到通知后的处理逻辑 * @param order 订单信息 */voidupdate(Orderorder);}

2. 定义被观察者(Subject)

/** * 被观察者 - 订单事件发布者 */publicclassOrderEventPublisher{/** 观察者列表:谁订阅了,谁就在这里面 */privatefinalList<Observer>observers=newArrayList<>();/** * 订阅 */publicvoidattach(Observerobserver){observers.add(observer);}/** * 取消订阅 */publicvoiddetach(Observerobserver){observers.remove(observer);}/** * 通知所有观察者:广播模式 */publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){observer.update(order);}}}

3. 具体观察者——各干各的

/** * 库存扣减观察者 */publicclassInventoryObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("库存扣减:商品 "+order.getProductId()+" 扣减 "+order.getQuantity());}}/** * 短信通知观察者 */publicclassSmsObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("短信通知:订单 "+order.getOrderId()+" 已支付,请尽快发货");}}/** * 积分变更观察者 */publicclassPointsObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("积分变更:用户 "+order.getUserId()+" 增加 "+order.getAmount()+" 积分");}}

4. 客户端调用

publicclassOrderService{privatefinalOrderEventPublisherpublisher=newOrderEventPublisher();publicOrderService(){// 订阅:谁该干活,自己注册publisher.attach(newInventoryObserver());publisher.attach(newSmsObserver());publisher.attach(newPointsObserver());// 以后要加功能?new 一个 Observer,attach 进来就行!}/** * 订单支付成功 */publicvoidpayOrder(Orderorder){// 1. 核心业务:更新订单状态order.setStatus(OrderStatus.PAID);System.out.println("订单 "+order.getOrderId()+" 支付成功");// 2. 发布事件:一嗓子喊完,各干各的publisher.notifyObservers(order);}}

看明白了吗?

OrderService只负责核心逻辑——更新订单状态。

至于库存、短信、积分?它根本不管!

以后要加优惠券?加两行代码:

publisher.attach(newCouponObserver());// 搞定!

订单核心逻辑一行都不用改!

这就是观察者模式的威力——开闭原则的完美体现。


五、JDK 自带观察者:别重复造轮子

Java 其实早就内置了观察者模式的支持:

  • java.util.Observable(被观察者)
  • java.util.Observer(观察者接口)

用法很简单:

// 被观察者publicclassOrderObservableextendsObservable{publicvoidorderPaid(Orderorder){setChanged();// 标记状态已改变notifyObservers(order);// 通知观察者}}// 观察者publicclassInventoryObserverimplementsObserver{@Overridepublicvoidupdate(Observableo,Objectarg){Orderorder=(Order)arg;System.out.println("库存扣减:"+order.getProductId());}}

但是注意——ObservableObserverJava 9 已被标记为 @Deprecated

官方理由:

  1. Observable是类不是接口,无法继承其他类
  2. setChanged()是 protected,扩展不灵活
  3. notifyObservers()是同步的,线程不安全
  4. 整体设计老旧,不符合现代 Java 风格

官方推荐用java.beans.PropertyChangeListener或第三方框架替代。


六、实战推荐:Spring 事件机制才是 Javaer 的首选

在企业级 Spring 项目中,观察者模式的最佳实践是 Spring Event

1. 定义事件

/** * 订单支付事件 */publicclassOrderPaidEventextendsApplicationEvent{privatefinalOrderorder;publicOrderPaidEvent(Objectsource,Orderorder){super(source);this.order=order;}publicOrdergetOrder(){returnorder;}}

2. 发布事件

@ServicepublicclassOrderService{@AutowiredprivateApplicationEventPublishereventPublisher;publicvoidpayOrder(Orderorder){// 1. 核心业务order.setStatus(OrderStatus.PAID);// 2. 发布事件:一行搞定eventPublisher.publishEvent(newOrderPaidEvent(this,order));}}

3. 监听事件——各模块自己管自己

@ComponentpublicclassInventoryListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){Orderorder=event.getOrder();System.out.println("库存扣减:"+order.getProductId());}}@ComponentpublicclassSmsListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){Orderorder=event.getOrder();System.out.println("短信通知:订单 "+order.getOrderId()+" 已支付");}}

优势明显

  • 完全解耦:发布者不知道谁在监听
  • 零侵入:新功能加个@EventListener就行
  • Spring 原生:不用自己维护观察者列表
  • 支持异步:加个@Async就能异步处理

4. 异步监听(性能优化利器)

@ComponentpublicclassPointsListener{@Async@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){// 积分变更不急,异步处理就行Orderorder=event.getOrder();System.out.println("异步积分变更:"+order.getUserId());}}

以为短信是同步阻塞的?加个@Async秒变异步,订单接口响应速度直接翻倍。


七、观察者模式 vs 其他模式(别搞混了)

对比项观察者模式策略模式发布-订阅模式
核心目的一对多通知算法替换消息解耦
耦合度松耦合(知道对方存在)松耦合完全解耦(通过 Broker)
通信方式直接调用直接调用通过消息中间件
典型场景事件通知支付方式切换Kafka/RabbitMQ
Java 实现Observer/EventStrategy 接口MessageQueue

最容易混淆的:观察者模式 vs 发布-订阅模式

  • 观察者:Subject 直接调用 Observer,两者有直接依赖
  • 发布-订阅:通过 EventBroker 中转,发布者和订阅者互不知道

简单说:观察者是"当面喊",发布-订阅是"发群里"。


八、避坑指南(这坑我替你踩过了)

我踩过的坑

坑 1:观察者执行异常导致链路中断

publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){observer.update(order);// 如果某个 Observer 抛异常,后面的全挂!}}

修复:加 try-catch,一个挂了不影响其他人

publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){try{observer.update(order);}catch(Exceptione){log.error("观察者执行异常: {}",observer.getClass().getSimpleName(),e);}}}

别急着 Ctrl+C / Ctrl+V,先想清楚异常会不会传染。


坑 2:Spring @EventListener 默认同步执行

如果某个监听器很慢(比如发邮件),整个订单接口都会被阻塞

修复

@Async@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){// 耗时操作放这里}

别忘了在启动类加@EnableAsync


坑 3:观察者注册顺序依赖

如果库存扣减必须在积分变更之前执行——观察者模式本身不保证顺序

修复:Spring Event 支持@Order注解

@Component@Order(1)publicclassInventoryListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){...}}@Component@Order(2)publicclassPointsListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){...}}

容易忽略的问题

  • 内存泄漏:观察者没及时 detach,对象无法被 GC 回收(尤其是 Android 开发中常见)
  • 死循环:观察者 A 更新后触发了被观察者状态变化,又被通知,又更新……无限循环
  • 并发问题:多线程下观察者列表的增删和遍历可能抛ConcurrentModificationException

线上环境注意事项

  • 异步观察者一定要加异常处理,否则错误会被吞掉,日志都看不到
  • 生产环境建议使用Spring Event + @Async + 线程池,别用默认线程池
  • 关键业务通知考虑用消息队列(RocketMQ/Kafka)替代内存级观察者,防止服务重启丢消息

性能建议

  • 观察者数量控制在合理范围(别搞 50 个监听者,那是设计有问题)
  • 耗时操作务必异步化
  • 高频事件注意节流和防抖

安全建议

  • 敏感操作不要放观察者中无鉴权执行
  • 事件对象中不要传递敏感信息的明文(如密码、密钥)
  • 监听器中的异常日志脱敏处理,别把用户隐私打到日志里

九、总结

项目说明
模式类型行为型模式
核心思想一对多依赖,状态变化自动通知
解决问题对象间紧耦合、if-else 嵌套地狱
实现方式手写 Observer / JDK Observable(已废弃)/ Spring Event
推荐方案Spring Event + @Async
适用场景事件通知、消息广播、状态变更联动

一句话总结

观察者模式的本质就是——你别来找我,我变了会通知你。

这跟现实生活中的道理一样:

与其天天盯着领导脸色(轮询),不如等领导发通知(观察者)。


技术不难,难的是没人告诉你坑在哪。
如果这篇帮到你,点个赞,顺便帮我抢救一下发际线 😄

👇 三连支持,动力源泉

如果这篇文章帮你省下了踩坑的时间,欢迎:

🔹点赞—— 让更多人看到这篇干货
🔹在看—— 你的认可是我持续输出的动力
🔹转发—— 分享给身边正在做AI Agent的朋友

你的每一个小动作,对我都很重要 ❤️


🙏 关于作者

你好,我是空门技术栈,一个常年和Bug战斗、持续填坑的Java开发者。

专注分享:

  • ✅ Java / Spring Boot / Spring AI Alibaba 企业级实战
  • ✅ RAG知识库、AI Agent、多智能体协作落地经验
  • ✅ Docker部署、微服务架构、线上问题排查
  • ✅ 偶尔聊聊「如何保住头发」这类程序员终极话题 😂

不搞水文,不贩卖焦虑,只写能跑通、能落地、能帮你少加班的实战内容。

关注我,咱们一起少踩坑,多写优雅代码。


📖 更多干货推荐

  • 告别手动复制接口文档!Apifox MCP + AI 自动测试让开发效率起飞
  • 别再纠结学哪个了!Java AI 三大框架深度对比:Spring AI vs AgentScope-Java
  • MySQL MCP Server 从零安装到使用实战,AI 直接查询数据库
  • Spring 注入三剑客:@Resource、@Autowired、@RequiredArgsConstructor 到底该用哪个?
  • Spring Event 用了三年,同事一句话把我问懵了
  • Java 抽象类(Abstract Class)彻底讲透:从基础到多态实战
  • Spring AI Alibaba 多智能体(Multi-agent)实战:6 大协作模式 + 完整代码
  • Spring AI Alibaba 智能体作为工具实战:别再让主 Agent 当"人肉路由器"了

🎁 粉丝福利

免费获取方式
私信空门技术栈,领取源码包。

有任何问题,也欢迎后台私信,门主看到都会回复~

💬 对文章内容有疑问?想看下一篇写什么主题?

直接在评论区留言,你的每一条评论我都会看。说不定下一篇文章的主题,就来自你的问题!


🤝 项目合作 / 技术咨询

平时工作之余,也会接一些技术项目和咨询,主要方向:

⚔️企业级开发

  • Java / Spring Boot 项目开发与重构
  • 微服务架构设计与落地
  • 系统性能调优、线上问题排查

🤖AI 应用落地(这是我最近的主力方向)

  • Spring AI Alibaba / RAG / Agent 应用开发
  • 企业私有知识库搭建
  • AI能力接入现有业务系统
  • 大模型本地化部署与调优

🛠️技术顾问 / 疑难Bug排查

  • 项目架构评审与方案设计
  • 线上疑难问题定位解决
  • 技术选型与团队培训

如果你正遇到以下情况,欢迎找我聊聊:

  • ✅ 想做AI项目,但技术方案拿不准
  • ✅ 项目卡在某个Bug上很久,团队搞不定
  • ✅ 想把AI接入现有业务,不知道从哪下手
  • ✅ 需要靠谱的开发外包或长期技术顾问

📮联系渠道(按回复速度排序)

  1. 最快:私信空门技术栈
  2. 邮件:2929119150@qq.com(请注明来意和具体需求)

一个人踩坑,是事故;一群人踩坑,就是《避坑宝典》。

—— IT 空门,与诸君共修技术大道 😎

http://www.jsqmd.com/news/1163837/

相关文章:

  • 2026最新适合长期学习的八字排盘App推荐:为什么要看AI提示词和多盘复盘?
  • 达梦数据库-非常规场景恢复-01-REDO损坏、误删
  • GEO服务商 vs SEO公司:本质区别、实施路径与佛山企业选择指南 - 米諾
  • XUnity Auto Translator:打破语言障碍,畅玩全球Unity游戏
  • 选购冷库专用把手铰链总踩坑?源头厂家实用选品技巧全方位参考 - 米諾
  • 高效实现Blender与MMD互通:MMD Tools插件完整实战指南
  • 【2013-11-03】Android应用开发笔记:inflate
  • 2026长春二道区管道疏通测评|正规马桶下水道疏通、化粪池清理、管道改造、首饰打捞靠谱推荐(春城到家实测避坑) - 资讯纵览
  • 医用远程会诊系统:同屏会诊模块的设计与实现
  • 挂失登报多少钱?2026新收费标准+办理全流程详解
  • AI 测试平台:怎么做的更好?
  • MiMo Code终端SSH工作流:AI编程环境与本地Linux无缝融合
  • 2026南京鼓楼免砸砖防水口碑实测,16个真实案例:卫生间漏水不砸砖到底行不行? - 苏易房屋修缮
  • 2026市区考生去南沙读高中划算吗?朝晖高级中学寄宿管理与升学优势 - 米諾
  • 3步解锁NVIDIA Profile Inspector隐藏设置:让显卡性能起飞
  • 如何评估一个 Skill 是否真的有效:从主观打分到工程化验证
  • TLP241A与PIC18F4553的电气隔离设计实践
  • 解决 VSCode 代码提示慢的一批的异常问题!
  • 开源大模型风险升级:DeepSeek 之后,模型供应链会被监管吗?
  • Java 变量类型实战:局部、成员、静态变量 3 大作用域与生命周期对比
  • 2026北海海城区黄金变现切勿冲动!7月实时回收金价更新,首选三大30年老品牌 - 福金阁黄金回收
  • BetterGI:终极原神自动化助手,告别重复劳动的智能游戏伴侣
  • 2026广州海珠区全屋搬家多少钱?小区小型搬运流程与高口碑搬家商家筛选要点 - gzdjxd
  • 2026年最值得推荐的企业获客软件(个人/团队/企业版) - 米諾
  • HarmonyOS运动健康——训练计划与动作库页面
  • Claude 复制内容带乱码太头疼?AI 导出鸭一键解决复制导出难题,程序员高效必备工具
  • 2026南京江宁免砸砖防水口碑实测,16个真实案例:卫生间漏水不砸砖到底行不行? - 苏易房屋修缮
  • 【RT-DETR涨点改进】25 从“能跑”到“跑得稳”:用C++封装RT-DETR推理SDK的实战心法
  • 基于Sentinel2的荒漠化指数计算
  • 前端链路追踪:用 OpenTelemetry 打通浏览器到服务端的性能数据