概要
简单说,观察者模式就是“一喊多应”的机制——一个对象(发布者)状态变了,所有依赖它的对象(订阅者)自动收到通知并更新自己。
想象一个微信群:你发了一条消息“今晚聚餐”,群里所有人(订阅者)都收到了通知,每个人可以决定怎么回复(更新自己的状态)。发消息的人不需要知道谁在群里,也不需要挨个私聊——这就是观察者模式的核心思想:发布者和订阅者解耦。
但软件设计里不止这一种“一喊多应”的模式。还有发布-订阅模式、中介者模式、责任链模式……它们看起来很像,但解决的是不同的问题。就像“喊人吃饭”和“喊人开会”虽然都是喊,但流程、目的、参与者都不一样。
整体架构流程
观察者模式的“舞台”
观察者模式有三个角色:
- 主题(Subject):被观察的对象,比如“天气站”。它维护一个订阅者列表,状态变化时挨个通知。
- 观察者(Observer):订阅者,比如“手机天气App”。它注册到主题上,收到通知后更新自己。
- 客户端(Client):负责把观察者注册到主题上,或者取消注册。
流程:
客户端把观察者注册到主题 → 主题状态变化 → 主题遍历所有观察者,调用它们的更新方法 → 每个观察者按自己的逻辑响应。
相关模式的“变种舞台”
我们对比三个最常混淆的模式:
| 模式 | 核心比喻 | 关键区别 |
|---|---|---|
| 观察者模式 | 微信群@所有人 | 发布者直接通知订阅者,订阅者之间不知道彼此 |
| 发布-订阅模式 | 微信公众号 | 发布者和订阅者之间有个“消息中心”做中间人 |
| 中介者模式 | 房产中介 | 所有对象通过一个中介通信,避免对象之间直接耦合 |
| 责任链模式 | 公司审批流程 | 请求沿着链条传递,直到有人处理 |
技术名词解释
1. 观察者模式(Observer Pattern)
定义:定义对象之间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都得到通知并自动更新。
生活类比:你订阅了一个YouTube频道(主题),频道更新时(状态变化),你(观察者)会收到推送通知。频道主不知道你是谁,你也不需要知道频道主怎么运营。
关键点:
- 直接通知:主题直接调用观察者的方法,没有中间人
- 同步(挨个通知):默认是同步的——主题通知观察者A,A处理完,再通知B,B处理完……就像老师点名,一个一个来
- 观察者知道主题:观察者需要持有主题的引用,才能注册和接收通知
2. 发布-订阅模式(Publish-Subscribe Pattern)
定义:发布者和订阅者通过一个消息代理(中间件)通信,发布者发送消息到特定频道,订阅者从频道接收消息。
生活类比:你订阅了“科技新闻”这个微信公众号(频道)。公众号作者(发布者)把文章发到微信平台(消息代理),微信平台再推送给所有订阅者。发布者不知道谁订阅了,订阅者也不知道谁发布了。
关键点:
- 间接通信:发布者和订阅者完全解耦,通过消息代理通信
- 异步(非实时):发布者发完消息就走,订阅者可以稍后处理(比如微信消息可以延迟接收)
- 支持多对多:一个发布者可以发到多个频道,一个订阅者可以订阅多个频道
3. 中介者模式(Mediator Pattern)
定义:用一个中介对象来封装一组对象之间的交互,使对象之间不需要显式相互引用。
生活类比:租房时,你(租客)和房东不直接联系,而是通过中介(中介者)沟通。中介负责传递信息、协调时间、处理纠纷。你和房东之间没有直接耦合。
关键点:
- 集中控制:所有通信都经过中介,中介可以控制交互逻辑
- 减少多对多:原本N个对象互相通信(N²条连接),变成每个对象只和中介通信(N条连接)
- 中介知道所有对象:中介持有所有参与者的引用,可以协调它们的行为
4. 责任链模式(Chain of Responsibility Pattern)
定义:将请求的发送者和接收者解耦,使多个接收者都有机会处理请求,将这些接收者连成一条链,沿着链传递请求直到被处理。
生活类比:你提交请假申请(请求):先到组长(处理者1),组长批不了(权限不够)→ 传到经理(处理者2),经理批了(处理成功)。每个处理者要么处理,要么传给下一个。
关键点:
- 链式传递:请求沿着链单向传递,直到被处理
- 每个处理者独立:每个处理者只知道自己下一个是谁,不知道链的全貌
- 处理者可以决定是否处理:可以处理并停止传递,也可以只处理一部分再继续传
技术细节
场景对比:用“通知系统”看区别
假设你要设计一个“股票价格变动通知系统”:
场景A:用观察者模式
- 股票价格类(主题)维护一个“订阅者列表”(比如手机App、大屏幕、邮件服务)
- 价格变化时,主题直接调用每个订阅者的
update(price)方法 - 问题:如果订阅者很多(比如10万个),主题要挨个通知,可能阻塞(卡住)。而且主题需要知道所有订阅者的接口(方法签名)
场景B:用发布-订阅模式
- 股票价格类(发布者)把价格变化消息发到“股票频道”(消息队列)
- 手机App、大屏幕、邮件服务(订阅者)各自从频道拉取消息
- 好处:发布者不需要知道谁订阅了,订阅者可以异步处理(比如手机App收到消息后慢慢更新UI,不影响价格更新)
- 典型实现:Redis的发布订阅、RabbitMQ消息队列
场景C:用中介者模式
- 股票价格类、手机App、大屏幕、邮件服务都注册到一个“系统中介者”
- 价格变化时,价格类通知中介者,中介者决定怎么通知其他组件(比如先通知大屏幕,再通知手机App,最后发邮件)
- 好处:中介者可以控制通知顺序、过滤重复通知、做日志记录
- 典型实现:MVC中的Controller(控制器)就是中介者,View(视图)和Model(模型)不直接通信
场景D:用责任链模式
- 股票价格变化时,请求先到“价格过滤器”(比如只处理涨幅超过5%的)
- 过滤器处理不了(涨幅不够)→ 传给“告警处理器”(触发告警)
- 告警处理器处理完→ 传给“日志记录器”(记录到数据库)
- 好处:每个处理器只负责一件事,可以灵活组合(比如可以调整过滤器的顺序)
选择指南:什么时候用哪个?
| 需求 | 推荐模式 | 理由 |
|---|---|---|
| 一个对象变化,多个对象需要更新,且更新逻辑简单 | 观察者模式 | 实现简单,直接通知 |
| 发布者和订阅者完全解耦,需要异步处理 | 发布-订阅模式 | 消息代理解耦,支持高并发 |
| 多个对象之间的交互逻辑复杂,需要集中控制 | 中介者模式 | 中介者统一管理交互逻辑 |
| 请求需要经过多个处理步骤,每个步骤独立 | 责任链模式 | 灵活组合处理链,容易扩展 |
代码级对比(伪代码示意)
// 观察者模式:主题直接通知 class StockPrice { List<Observer> observers; void notifyObservers() { for (Observer o : observers) { o.update(price); // 直接调用,同步 } } } // 发布-订阅模式:通过消息代理 class MessageBroker { Map<String, List<Subscriber>> channels; void publish(String channel, Message msg) { for (Subscriber s : channels.get(channel)) { s.receive(msg); // 可以是异步的 } } } // 中介者模式:通过中介通信 class Mediator { void notify(Component sender, Event event) { if (event == "priceChange") { display.update(event); // 中介决定顺序 email.send(event); } } } // 责任链模式:链式传递 abstract class Handler { Handler next; void handle(Request req) { if (canHandle(req)) { process(req); } else if (next != null) { next.handle(req); // 传给下一个 } } }小结
观察者模式是“一喊多应”的起点,但它有局限性:同步通知、发布者知道订阅者。当系统变大、需要解耦时,就演化出了发布-订阅模式(加中间人)、中介者模式(集中控制)、责任链模式(链式处理)。
记住这个类比:
- 观察者模式 = 微信群@所有人(直接喊)
- 发布-订阅模式 = 微信公众号(通过平台推送)
- 中介者模式 = 房产中介(统一协调)
- 责任链模式 = 公司审批流程(逐级传递)
一句话总结:
如果只是“通知”就够了,用观察者;如果需要“解耦+异步”,用发布-订阅;如果需要“控制交互逻辑”,用中介者;如果需要“灵活处理链”,用责任链。
最后提醒:设计模式不是“哪个更好”,而是“哪个更合适”。就像工具箱里的螺丝刀和扳手——拧螺丝用螺丝刀,拧螺母用扳手,选对了工具,活就干得漂亮。