1. 观察者模式的核心价值与应用场景
观察者模式(Observer Pattern)是行为型设计模式中最常用的模式之一,它定义了对象之间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式在事件驱动系统、消息队列、GUI组件交互等场景中应用广泛。
我在实际开发中最深刻的体会是:观察者模式本质上解决的是"状态同步"问题。比如电商系统中,当商品库存发生变化时,需要同时更新购物车、订单系统、推荐系统的状态。如果采用硬编码的方式在各个模块中调用更新方法,会导致系统高度耦合,而观察者模式通过解耦发布者和订阅者,让状态变化能够自动传播到所有相关方。
1.1 模式结构解析
观察者模式包含四个核心角色:
- Subject(主题):维护一个观察者列表,提供添加、删除观察者的方法,以及通知观察者的机制。在Java中通常定义为接口,如:
public interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); }- ConcreteSubject(具体主题):实现Subject接口,维护具体状态,当状态变化时调用notifyObservers()。例如商品库存主题:
public class ProductInventory implements Subject { private List<Observer> observers = new ArrayList<>(); private int stock; public void setStock(int stock) { this.stock = stock; notifyObservers(); // 库存变化时自动通知 } // 实现其他接口方法... }- Observer(观察者):定义更新接口,通常只有一个update()方法:
public interface Observer { void update(int stock); }- ConcreteObserver(具体观察者):实现Observer接口,定义收到通知后的具体行为。例如购物车观察者:
public class CartObserver implements Observer { @Override public void update(int stock) { if(stock == 0) { System.out.println("购物车中该商品已下架"); } } }1.2 典型应用场景分析
观察者模式特别适合以下场景:
- 事件驱动系统:如用户注册成功后触发邮件发送、数据统计等操作
- GUI事件处理:按钮点击事件被多个组件监听
- 分布式系统状态同步:配置中心修改配置后通知所有服务节点
- 实时数据监控:服务器资源使用率超过阈值时触发告警
我在一个物流跟踪系统中曾这样应用:当运单状态变化时,需要同时更新客户APP、客服系统和物流调度看板。使用观察者模式后,新增一个监控系统只需实现Observer接口并注册,无需修改原有代码,完美符合开闭原则。
2. 观察者模式的实现方式与演进
2.1 经典实现与问题
传统观察者模式有几个常见问题:
- 通知顺序不可控:观察者被通知的顺序取决于注册顺序
- 同步阻塞问题:主题通知观察者是同步调用,一个观察者处理慢会影响整体
- 内存泄漏风险:观察者未正确注销会导致无法被GC回收
针对这些问题,现代框架提供了更完善的实现方案。
2.2 Java内置支持
Java.util包提供了Observable类和Observer接口:
// 被观察者继承Observable public class WeatherData extends Observable { private float temperature; public void setMeasurements(float temp) { this.temperature = temp; setChanged(); // 标记状态已改变 notifyObservers(); // 通知观察者 } } // 观察者实现Observer接口 public class CurrentConditionsDisplay implements Observer { @Override public void update(Observable o, Object arg) { if(o instanceof WeatherData) { // 处理更新 } } }注意:Java 9开始Observable被标记为deprecated,推荐使用PropertyChangeListener等更灵活的机制。
2.3 Spring框架的事件机制
Spring提供了更强大的应用内事件机制:
// 定义事件 public class OrderCreatedEvent extends ApplicationEvent { public OrderCreatedEvent(Order source) { super(source); } } // 发布事件 @Service public class OrderService { @Autowired private ApplicationEventPublisher eventPublisher; public void createOrder(Order order) { // 创建订单逻辑... eventPublisher.publishEvent(new OrderCreatedEvent(order)); } } // 监听事件 @Component public class InventoryListener { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // 扣减库存逻辑 } }Spring事件机制的优势:
- 支持异步事件处理(通过@Async注解)
- 支持条件化监听(@EventListener的condition属性)
- 与Spring生态无缝集成
3. 观察者模式的进阶实践
3.1 解决内存泄漏的几种方案
- 显式注销:在观察者生命周期结束时调用removeObserver()
public class ShoppingCart { private Subject inventorySubject; public void destroy() { inventorySubject.removeObserver(this); // 显式注销 } }- 弱引用观察者:使用WeakReference持有观察者
public class WeakObserver implements Observer { private WeakReference<Observer> weakRef; public WeakObserver(Observer realObserver) { this.weakRef = new WeakReference<>(realObserver); } @Override public void update() { Observer observer = weakRef.get(); if(observer != null) { observer.update(); } } }- 自动清理机制:定期检查观察者是否有效
public class InventorySubject implements Subject { private List<Observer> observers = new CopyOnWriteArrayList<>(); public void cleanDeadObservers() { observers.removeIf(obs -> { try { return ((WeakReference)obs).get() == null; } catch(Exception e) { return false; } }); } }3.2 异步观察者模式实现
使用线程池实现异步通知:
public class AsyncSubject implements Subject { private ExecutorService executor = Executors.newFixedThreadPool(4); private List<Observer> observers = new CopyOnWriteArrayList<>(); @Override public void notifyObservers() { for(Observer observer : observers) { executor.submit(() -> observer.update()); } } }注意事项:异步实现需要考虑线程安全和顺序问题,CopyOnWriteArrayList能解决并发修改问题,但无法保证通知顺序。
3.3 观察者模式与发布/订阅模式的区别
虽然两者都用于解耦生产者和消费者,但有重要区别:
| 特性 | 观察者模式 | 发布/订阅模式 |
|---|---|---|
| 耦合度 | 松耦合(接口依赖) | 完全解耦(通过中间件) |
| 通信方式 | 直接调用观察者方法 | 通过消息代理 |
| 实时性 | 同步/异步都可 | 通常是异步 |
| 典型实现 | Java Observer, Spring Event | Kafka, RabbitMQ |
| 适用场景 | 单应用内部通信 | 跨系统、分布式通信 |
4. 实战案例:电商库存通知系统
4.1 需求分析
假设我们需要实现以下功能:
- 当商品库存变化时,通知购物车、订单系统和推荐系统
- 库存为0时,购物车需要移出相关商品
- 库存恢复时,订单系统需要通知等待中的客户
- 推荐系统需要根据库存变化调整推荐策略
4.2 类图设计
@startuml class ProductInventory { - observers: List<Observer> - stock: int + setStock(int stock) + registerObserver(Observer o) + removeObserver(Observer o) + notifyObservers() } interface Observer { + update(int stock) } class CartObserver { + update(int stock) } class OrderObserver { + update(int stock) } class RecommendObserver { + update(int stock) } ProductInventory o-- Observer ProductInventory <|.. CartObserver ProductInventory <|.. OrderObserver ProductInventory <|.. RecommendObserver @enduml4.3 完整实现代码
// 主题接口 public interface StockSubject { void registerObserver(StockObserver o); void removeObserver(StockObserver o); void notifyObservers(); } // 具体主题 public class ProductInventory implements StockSubject { private List<StockObserver> observers = new ArrayList<>(); private String productId; private int stock; public ProductInventory(String productId, int stock) { this.productId = productId; this.stock = stock; } public void setStock(int newStock) { int oldStock = this.stock; this.stock = newStock; if(oldStock != newStock) { notifyObservers(); } } @Override public void registerObserver(StockObserver o) { observers.add(o); } @Override public void removeObserver(StockObserver o) { observers.remove(o); } @Override public void notifyObservers() { for(StockObserver observer : observers) { observer.onStockChanged(productId, stock); } } } // 观察者接口 public interface StockObserver { void onStockChanged(String productId, int newStock); } // 购物车观察者 public class CartObserver implements StockObserver { @Override public void onStockChanged(String productId, int newStock) { if(newStock == 0) { System.out.println("从购物车移除缺货商品: " + productId); // 实际业务中调用购物车服务API } } } // 订单观察者 public class OrderObserver implements StockObserver { @Override public void onStockChanged(String productId, int newStock) { if(newStock > 0) { System.out.println("通知等待商品补货的订单: " + productId); // 查询等待该商品的订单并通知 } } } // 使用示例 public class Main { public static void main(String[] args) { ProductInventory inventory = new ProductInventory("P1001", 10); // 注册观察者 inventory.registerObserver(new CartObserver()); inventory.registerObserver(new OrderObserver()); // 库存变化触发通知 inventory.setStock(0); // 触发缺货通知 inventory.setStock(5); // 触发补货通知 } }4.4 性能优化技巧
- 批量通知:当需要频繁更新时,可以积累变化后批量通知
public class ProductInventory { private boolean changed = false; public void setStock(int newStock) { this.stock = newStock; this.changed = true; } public void flushChanges() { if(changed) { notifyObservers(); changed = false; } } }- 差异化通知:只通知关注特定变化的观察者
public interface StockObserver { void onStockIncreased(String productId, int newStock); void onStockDecreased(String productId, int newStock); void onStockSoldOut(String productId); }- 使用观察者管理器:集中管理观察者生命周期
public class ObserverManager { private Map<String, List<StockObserver>> productObservers = new ConcurrentHashMap<>(); public void subscribe(String productId, StockObserver observer) { productObservers.computeIfAbsent(productId, k -> new CopyOnWriteArrayList<>()) .add(observer); } public void notify(String productId, int stock) { List<StockObserver> observers = productObservers.get(productId); if(observers != null) { observers.forEach(o -> o.onStockChanged(productId, stock)); } } }5. 常见问题与解决方案
5.1 观察者处理失败的影响
问题现象:某个观察者处理通知时抛出异常,导致后续观察者无法收到通知。
解决方案:
- 为每个观察者添加try-catch块
public void notifyObservers() { for(Observer observer : observers) { try { observer.update(); } catch(Exception e) { log.error("Observer处理失败", e); } } }- 使用责任链模式包装观察者
public class SafeObserver implements Observer { private Observer delegate; public SafeObserver(Observer delegate) { this.delegate = delegate; } @Override public void update() { try { delegate.update(); } catch(Exception e) { // 处理异常 } } }5.2 循环通知问题
问题现象:观察者A收到通知后修改主题状态,导致再次通知,形成无限循环。
解决方案:
- 添加修改标记
public class ProductInventory { private boolean notifying = false; public void setStock(int stock) { this.stock = stock; if(!notifying) { notifying = true; notifyObservers(); notifying = false; } } }- 使用版本号控制
public void setStock(int stock) { if(this.stock != stock) { this.stock = stock; this.version++; // 每次修改递增版本号 notifyObservers(); } } public void update() { if(this.lastVersion < subject.getVersion()) { // 处理更新 this.lastVersion = subject.getVersion(); } }5.3 观察者性能瓶颈
问题现象:观察者数量过多或处理逻辑复杂导致系统性能下降。
优化方案:
- 分级通知:重要观察者立即通知,次要观察者延迟通知
public void notifyObservers() { // 立即通知核心观察者 priorityObservers.forEach(this::notifyImmediately); // 异步通知普通观察者 normalObservers.forEach(this::notifyAsync); }- 观察者分组:按业务维度分组,只通知相关组
public enum ObserverGroup { ORDER, INVENTORY, RECOMMENDATION } public void notifyGroup(ObserverGroup group) { observers.stream() .filter(o -> o.getGroup() == group) .forEach(Observer::update); }- 使用反应式编程:如RxJava、Project Reactor
public class ReactiveInventory { private final Subject<Integer> stockSubject = ReplaySubject.create(1); public Observable<Integer> stockChanges() { return stockSubject; } public void setStock(int stock) { this.stock = stock; stockSubject.onNext(stock); } } // 订阅 inventory.stockChanges() .filter(stock -> stock > 0) .subscribe(stock -> System.out.println("库存更新: " + stock));6. 模式扩展与替代方案
6.1 观察者模式的变体
- 中介者模式:当观察者之间也需要交互时,可以引入中介者协调
- 事件总线:集中管理事件和监听器,支持全局事件分发
- 响应式流:基于回压机制处理观察者跟不上生产者速度的问题
6.2 现代替代方案比较
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 传统观察者模式 | 简单直接,语言级支持 | 功能有限,缺乏高级特性 | 简单通知场景 |
| Spring事件机制 | 与Spring整合好,支持异步 | 依赖Spring框架 | Spring应用内部事件 |
| 消息队列(Kafka等) | 解耦彻底,支持持久化/重试 | 复杂度高,需要基础设施 | 分布式系统跨服务通知 |
| 响应式编程(RxJava等) | 强大的流处理能力 | 学习曲线陡峭 | 复杂事件流处理 |
6.3 观察者模式与CQRS架构
在CQRS(命令查询职责分离)架构中,观察者模式常用于:
- 将写模型的状态变化同步到读模型
- 实现事件溯源中的事件投影
- 更新多个读模型的缓存
典型实现流程:
- 命令处理修改聚合根状态
- 聚合根发布领域事件
- 观察者订阅事件并更新读模型
// 命令处理 public class ProductCommandService { public void updateStock(String productId, int quantity) { Product product = repository.findById(productId); product.updateStock(quantity); repository.save(product); // 发布事件 eventBus.publish(new StockUpdatedEvent(productId, product.getStock())); } } // 读模型更新 public class ProductViewProjection { @Subscribe public void handle(StockUpdatedEvent event) { // 更新商品列表视图 productViewRepository.updateStock(event.getProductId(), event.getNewStock()); // 更新库存预警视图 warningService.checkStock(event.getProductId(), event.getNewStock()); } }在实际项目中,观察者模式往往不是单独存在,而是与其他模式组合使用。比如与工厂模式结合创建不同类型的观察者,与装饰者模式结合增强观察者功能,与策略模式结合实现可替换的通知策略等。这种模式组合能够发挥更强大的威力。