观察者模式有哪些角色?通知流程是什么?
简化版
观察者模式主要有 Subject、Observer、ConcreteSubject、ConcreteObserver 四类角色。流程是观察者先订阅主题,主题状态变化后遍历观察者列表并调用通知方法,观察者收到事件后执行自己的处理逻辑。
详细版
观察者模式的角色可以这样拆:
- Subject:主题接口,提供注册、移除、通知观察者的方法。
- ConcreteSubject:具体主题,保存状态和观察者列表,在状态变化时通知。
- Observer:观察者接口,定义统一的更新方法。
- ConcreteObserver:具体观察者,收到通知后执行具体动作。
典型通知流程:
- 观察者调用
attach注册到主题; - 主题内部维护观察者集合;
- 主题状态发生变化;
- 主题调用
notifyObservers; - 每个观察者执行自己的
update方法。
在工程项目中,主题不一定手写观察者列表,也可能由 Spring 容器、事件总线或消息队列维护订阅关系。
完整版教学
一、Subject 是事件源
Subject 可以理解为“事件源”或“被观察者”。它负责维护观察者关系,并在状态变化时发出通知。
一个简化接口可以这样写:
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(Event event);
}
这里的重点不是方法名,而是职责:Subject 管理订阅关系并触发通知。
在实际项目里,Subject 可能不是一个传统对象,而是事件发布器,例如:
ApplicationEventPublisher;- EventBus;
- 消息队列 Producer;
- 前端事件中心。
二、Observer 是统一响应接口
Observer 定义观察者收到通知后应该执行什么方法。
public interface Observer {
void update(Event event);
}
所有具体观察者都实现这个接口。主题只依赖 Observer 抽象,不依赖短信观察者、积分观察者、日志观察者这些具体类。
这就是观察者模式的解耦点。
三、ConcreteObserver 承载具体动作
具体观察者只处理自己关心的逻辑。
public class SmsObserver implements Observer {
public void update(Event event) {
// 发送短信
}
}
public class CouponObserver implements Observer {
public void update(Event event) {
// 发放优惠券
}
}
每个观察者应该尽量职责单一。如果一个观察者里又写了短信、积分、画像、日志,观察者本身就变成新的上帝类了。
四、通知流程的关键是“订阅在前,通知在后”
观察者模式不是凭空知道谁要响应事件。必须先建立订阅关系。
注册观察者
-> 保存到观察者列表
-> 主题状态变化
-> 遍历观察者
-> 调用 update
如果观察者没有注册,主题通知时就不会触发它。这也是线上排查时要注意的点:事件没有被处理,不一定是事件没发,也可能是观察者没注册成功。
五、事件对象让通知更稳定
观察者的 update 方法可以只传一个事件对象,而不是传一堆零散参数。
public class OrderPaidEvent {
private Long orderId;
private Long userId;
private BigDecimal amount;
private LocalDateTime paidAt;
}
这样有几个好处:
- 事件语义清晰;
- 后续扩展字段更方便;
- 观察者可以只取自己需要的字段;
- 日志和排查更容易。
六、Subject、Observer 与事件对象的协作
Subject 当前注册 3 个观察者,notify 时通常遍历 3 次;移除 1 个后下一次只应通知剩余 2 个。
register -> snapshot observers -> notify(event) -> each observer
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
七、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 事件对象应表达已经发生的事实,并携带处理所需的最小稳定信息。 |
| 适用边界 | 通知时直接遍历可变列表可能遇到监听器自移除导致并发修改;复制快照或并发容器更稳妥。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:事件对象应表达已经发生的事实,并携带处理所需的最小稳定信息。
八、常见误区与追问
- 误区:Subject 只负责保存观察者,不负责发通知。 维护订阅和在恰当时机通知都属于 Subject 协作职责,具体存储可委托事件总线。
- 误区:发布事件天然等于异步和最终一致性。 观察者只定义一对多通知关系;同步还是异步、是否跨进程以及一致性语义都由具体实现决定。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:通知顺序是观察者模式保证的吗? 不是;除非具体实现定义排序,否则观察者不应依赖注册顺序。
- 追问:观察者失败后由谁重试? 模式本身没有答案;同步场景要定义异常传播,异步场景要由执行器、事件总线或消息系统提供重试与死信策略。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
九、加强记忆
观察者模式的角色记成“主题管订阅,观察者管响应”。通知流程是先注册,再状态变化,最后主题遍历观察者并调用统一方法;事件对象负责携带上下文数据,让观察者之间保持解耦。