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