观察者模式:从博客订阅到消息队列的解耦实践
一、模式核心:用事件驱动实现对象间松耦合
在新闻 APP 中,当热点事件发生时需要实时通知所有订阅用户;在电商系统中,库存变化需触发价格监控模块重新计算。这类场景的核心矛盾是:对象间存在依赖关系,但不能硬编码耦合。** 观察者模式(Observer Pattern)** 通过定义「发布 - 订阅」模型,让对象间的通知关系完全解耦,核心解决:
- 数据变更通知:当主题(Subject)状态变化时,自动通知所有关联的观察者(Observer)
- 模块解耦:观察者与主题无需互相知道具体实现,仅通过抽象接口交互
核心思想与 UML 类图

二、核心实现:构建通用事件通知框架
1. 定义主题接口(Subject)
java
import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
private List<Observer> observers = new ArrayList<>();
// 注册观察者
public void attach(Observer observer) {
observers.add(observer);
}
// 注销观察者
public void detach(Observer observer) {
observers.remove(observer);
}
// 通知所有观察者
protected void notifyObservers(Object data) {
observers.forEach(observer -> observer.update(data));
}
// 抽象方法:主题状态变化时调用
public abstract void updateState(Object newState);
}
2. 实现具体主题(ConcreteSubject)
java
public class StockSubject extends Subject {
private double stockPrice; // 主题状态:股票价格
public double getStockPrice() {
return stockPrice;
}
@Override
public void updateState(Object newState) {
this.stockPrice = (double) newState;
notifyObservers(stockPrice); // 状态变化时触发通知
}
}
3. 定义观察者接口(Observer)
java
public interface Observer {
// 接收主题通知的更新方法
void update(Object data);
}
4. 实现具体观察者(ConcreteObserver)
java
public class InvestorObserver implements Observer {
private String investorName;
public InvestorObserver(String name) {
this.investorName = name;
}
@Override
public void update(Object data) {
double price = (double) data;
System.out.println("投资者 " + investorName + " 收到通知:股票价格更新为 " + price);
// 执行具体业务逻辑(如触发交易策略)
}
}
5. 客户端调用示例
java
public class ClientDemo {
public static void main(String[] args) {
// 创建主题与观察者
StockSubject stock = new StockSubject();
Observer investorA = new InvestorObserver("张三");
Observer investorB = new InvestorObserver("李四");
// 注册观察者
stock.attach(investorA);
stock.attach(investorB);
// 主题状态变化,触发通知
stock.updateState(15.5); // 输出:两位投资者收到价格更新通知
stock.detach(investorB); // 注销观察者B
stock.updateState(16.2); // 仅投资者A收到通知
}
}
三、进阶:实现异步事件驱动与精准通知
1. 支持泛型的强类型通知
java
// 定义带泛型的观察者接口
public interface GenericObserver<T> {
void update(T data);
}
// 主题支持特定类型的事件数据
public class GenericSubject<T> {
private List<GenericObserver<T>> observers = new ArrayList<>();
public void notifyObservers(T data) {
observers.forEach(observer -> observer.update(data));
}
}
// 使用示例:股票价格变化事件(Double类型)
GenericSubject<Double> stock = new GenericSubject<>();
stock.attach((Double price) -> System.out.println("精准通知:价格" + price));
2. 异步通知机制(解耦通知与业务处理)
java
public class AsyncSubject<T> extends GenericSubject<T> {
private ExecutorService executor = Executors.newFixedThreadPool(5);
@Override
public void notifyObservers(T data) {
// 使用线程池异步执行观察者逻辑
executor.submit(() -> super.notifyObservers(data));
}
}
// 优势:避免主题阻塞,提升系统吞吐量
// 注意:需处理异步带来的线程安全与数据一致性问题
3. 带事件类型的精准通知(基于 Guava EventBus)
java
// 定义具体事件类型
public class PriceUpdateEvent {
private double newPrice;
public PriceUpdateEvent(double price) {this.newPrice = price;}
// getters...
}
// 使用Guava EventBus实现
EventBus eventBus = new EventBus();
eventBus.register(new Object() {
@Subscribe
public void onPriceUpdate(PriceUpdateEvent event) {
// 仅接收PriceUpdateEvent类型的事件
System.out.println("处理价格更新事件:" + event.getNewPrice());
}
});
eventBus.post(new PriceUpdateEvent(18.7)); // 精准触发对应观察者
四、框架与源码中的观察者模式实践
1. Spring ApplicationEvent(同步通知)
-
核心类 :
ApplicationEvent
(事件)、ApplicationListener
(观察者)、ApplicationEventPublisher
(主题) -
使用示例:
java// 定义自定义事件 public class OrderPaidEvent extends ApplicationEvent { private String orderId; public OrderPaidEvent(Object source, String orderId) { super(source); this.orderId = orderId; } } // 注册观察者 @Component public class OrderListener implements ApplicationListener<OrderPaidEvent> { @Override public void onApplicationEvent(OrderPaidEvent event) { // 处理订单支付后的业务(如更新库存、发送短信) } } // 发布事件 applicationEventPublisher.publishEvent(new OrderPaidEvent(this, "1001"));
2. Android Listener 机制(异步回调)
-
典型场景:按钮点击事件、网络请求回调
-
实现原理:
java// 主题(Button)注册观察者(OnClickListener) button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 观察者逻辑 } });
3. Kafka 消息队列(分布式观察者)
- 主题:Kafka 的 Topic(如 "stock-price-topic")
- 观察者:Kafka 消费者组(Consumer Group)
- 优势:支持百万级观察者的分布式通知,实现系统间的松耦合
java
// Kafka消费者示例(观察者)
KafkaConsumer<String, Double> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("stock-price-topic"));
while (true) {
ConsumerRecords<String, Double> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, Double> record : records) {
handlePriceUpdate(record.value()); // 处理通知
}
}
五、避坑指南:正确使用观察者模式的 4 个要点
1. 避免内存泄漏(及时注销观察者)
-
❌ 错误实践:观察者注册后未调用
detach()
,导致主题持有观察者强引用 -
✅ 最佳实践:在观察者销毁时(如 Activity 销毁),主动从主题中注销
java@Override protected void onDestroy() { super.onDestroy(); subject.detach(this); // 注销当前观察者 }
2. 控制通知粒度(避免过度频繁通知)
-
当主题状态变化频繁时(如实时数据流),增加
防抖机制
或批量通知
java// 防抖示例:1秒内多次变化合并为一次通知 public void updateState(Object newState) { if (lastUpdateTime + 1000 > System.currentTimeMillis()) { return; // 忽略短时间内的重复通知 } lastUpdateTime = System.currentTimeMillis(); super.updateState(newState); }
3. 处理循环依赖(观察者反向修改主题)
-
当观察者更新时可能再次触发主题状态变化,需增加
事件防火墙
javapublic void update(Object data) { if (isProcessingEvent) return; // 避免循环触发 isProcessingEvent = true; // 业务逻辑 isProcessingEvent = false; }
4. 反模式:滥用观察者导致复杂度上升
- 当观察者链过深(如 A→B→C→D)时,改用责任链模式 或事件总线重构
- 避免为简单的双向通信使用观察者(直接调用接口更高效)
六、总结:何时该用观察者模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
实时数据通知 | 一个对象状态变化需触发多个对象的响应 | 股票行情推送、邮件订阅系统 |
模块解耦需求 | 对象间存在依赖但不想硬编码关联 | 微服务事件驱动架构、GUI 事件处理 |
异步事件处理 | 需要将通知与业务逻辑分离 | 消息队列、日志异步写入 |
观察者模式通过「主题抽象 + 事件驱动」的设计,将对象间的依赖关系从「直接调用」转化为「事件订阅」,这是实现松耦合系统的核心模式之一。下一篇我们将深入探讨状态模式,解析有限状态机在电商订单系统中的设计与实现,敬请期待!
扩展思考:观察者模式 vs 发布 - 订阅模式
两者核心思想相似,但存在关键区别:
模式 | 中介者 | 通知方式 | 应用场景 |
---|---|---|---|
观察者模式 | 主题直接关联观察者 | 主题主动通知观察者 | 本地模块间通信 |
发布 - 订阅模式 | 事件总线作为中介 | 发布者与订阅者无直接关联 | 分布式系统、微服务间通信 |
理解这些差异,有助于在不同架构场景中选择最合适的设计方案。