观察者模式深度解析:从理论到生产实践,Spring都在用的设计模式
观察者模式UML类图
在软件开发中,我们经常需要实现”一个对象状态变化,多个对象自动更新”的场景。比如用户注册成功时,需要发送欢迎邮件、赠送积分、记录日志等多个操作。这种一对多的依赖关系,正是观察者模式的典型应用场景。
观察者模式是什么?
观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,所有观察者都会收到通知并自动更新。
这里借用建筑监理的比喻:投资方(观察者们)委托建筑师(主题)监督项目进度。每当有建筑进展,建筑师主动通知所有投资方,无需投资方反复询问。
核心角色与实现
观察者模式主要包含四个角色:
- Subject(主题):定义了被观察者对象的基本接口
- ConcreteSubject(具体主题):主题的具体实现,维护观察者列表
- Observer(观察者):定义了观察者对通知做出反应的方法
- ConcreteObserver(具体观察者):实现Observer接口,观察主题并执行相应操作
下面通过一个实际的电商库存管理示例来展示观察者模式的实现:
// 观察者接口 public interface InventoryObserver { void onInventoryChanged(Product product, int newQuantity); } // 具体观察者1:库存预警系统 public class InventoryAlert implements InventoryObserver { @Override public void onInventoryChanged(Product product, int newQuantity) { if (newQuantity < 10) { System.out.println("⚠️ 库存预警: " + product.getName() + " 库存不足,当前数量: " + newQuantity); } } } // 具体观察者2:订单处理系统 public class OrderProcessor implements InventoryObserver { @Override public void onInventoryChanged(Product product, int newQuantity) { if (newQuantity < 5) { System.out.println("📦 自动触发补货: " + product.getName() + " 正在向供应商下单..."); } } } // 具体观察者3:价格调整系统 public class PriceAdjuster implements InventoryObserver { @Override public void onInventoryChanged(Product product, int newQuantity) { if (newQuantity > 100) { System.out.println("💰 库存充足,可以考虑促销降价!"); } } } // 主题接口 public interface InventorySubject { void addObserver(InventoryObserver observer); void removeObserver(InventoryObserver observer); void notifyObservers(Product product, int quantity); } // 具体主题:库存管理器 public class InventoryManager implements InventorySubject { private final List<InventoryObserver> observers = new ArrayList<>(); private final Map<String, Integer> inventory = new HashMap<>(); @Override public void addObserver(InventoryObserver observer) { observers.add(observer); System.out.println("添加观察者: " + observer.getClass().getSimpleName()); } @Override public void removeObserver(InventoryObserver observer) { observers.remove(observer); System.out.println("移除观察者: " + observer.getClass().getSimpleName()); } @Override public void notifyObservers(Product product, int quantity) { for (InventoryObserver observer : observers) { observer.onInventoryChanged(product, quantity); } } public void updateStock(String productId, int quantity) { inventory.put(productId, quantity); Product product = new Product(productId, "Product-" + productId); notifyObservers(product, quantity); } } // 使用示例 public class ObserverDemo { public static void main(String[] args) { // 创建库存管理器(主题) InventoryManager manager = new InventoryManager(); // 创建观察者 InventoryAlert alert = new InventoryAlert(); OrderProcessor orderProcessor = new OrderProcessor(); PriceAdjuster priceAdjuster = new PriceAdjuster(); // 注册观察者 manager.addObserver(alert); manager.addObserver(orderProcessor); manager.addObserver(priceAdjuster); System.out.println("\n--- 模拟库存变化 ---"); manager.updateStock("iphone", 3); // 库存不足场景 System.out.println("\n--- 模拟供应商补货 ---"); manager.updateStock("iphone", 50); // 补货后库存正常 System.out.println("\n--- 模拟库存积压 ---"); manager.updateStock("iphone", 150); // 库存积压场景 } }运行结果:
添加观察者: InventoryAlert 添加观察者: OrderProcessor 添加观察者: PriceAdjuster --- 模拟库存变化 --- ⚠️ 库存预警: Product-iphone 库存不足,当前数量: 3 📦 自动触发补货: Product-iphone 正在向供应商下单... --- 模拟供应商补货 --- --- 模拟库存积压 --- 💰 库存充足,可以考虑促销降价!Spring框架中的事件机制
Spring框架中的事件机制
观察者模式在Spring框架中被广泛应用,其事件机制就是典型的实现。Spring的事件机制以ApplicationContext为核心,实现了观察者模式的高级封装:
// 自定义事件 public class UserRegisterEvent extends ApplicationEvent { private final String username; public UserRegisterEvent(Object source, String username) { super(source); this.username = username; } public String getUsername() { return username; } } // 事件监听器(观察者) @Component @EventListener public class EmailSendListener { @EventListener(classes = UserRegisterEvent.class) public void sendEmail(UserRegisterEvent event) { System.out.println("📧 发送欢迎邮件给: " + event.getUsername()); } } @Component @EventListener public class PointsGrantListener { @EventListener(classes = UserRegisterEvent.class) public void grantPoints(UserRegisterEvent event) { System.out.println("🎁 为新用户 " + event.getUsername() + " 赠送100积分"); } } @Component @EventListener public class LogRecordListener { @EventListener(classes = UserRegisterEvent.class) public void recordLog(UserRegisterEvent event) { System.out.println("📝 记录用户注册日志: " + event.getUsername()); } } // 事件发布者 @RestController @RequestMapping("/api/users") public class UserController { @Autowired private ApplicationEventPublisher eventPublisher; @PostMapping("/register") public ResponseEntity<String> register(@RequestParam String username) { // 注册用户... System.out.println("创建用户: " + username); // 发布事件 UserRegisterEvent event = new UserRegisterEvent(this, username); eventPublisher.publishEvent(event); return ResponseEntity.ok("注册成功"); } }Spring的事件机制优势:
- 完全解耦:发布者和订阅者互不感知
- 异步支持:支持@Async异步事件处理
- 事务绑定:@TransactionalEventListener支持事务提交后触发
- 条件过滤:@EventListener支持SpEL条件表达式
消息队列中的观察者模式
消息队列系统(如RabbitMQ、Apache Kafka)本质上也是观察者模式的延伸,但采用了更灵活的发布-订阅模式:
// 配置多队列消息监听 @Component @RabbitListener public class OrderEventHandler { // 监听订单创建事件 @RabbitListener(queues = "orderCreated") public void handleOrderCreated(Order order) { log.info("订单创建事件: {}", order.getId()); // 1. 减库存 inventoryService.reduceStock(order); // 2. 发送确认邮件 emailService.sendOrderConfirm(order); // 3. 记录业务日志 auditService.log(order); } // 监听订单支付事件 @RabbitListener(queues = "orderPaid") public void handleOrderPaid(String orderId) { log.info("订单支付事件: {}", orderId); // 1. 更新订单状态 orderService.payOrder(orderId); // 2. 通知物流系统发货 logisticsService.shipOrder(orderId); // 3. 更新用户累计消费 userService.addConsumption(orderId); } // 监听订单取消事件 @RabbitListener(queues = "orderCancelled") public void handleOrderCancelled(Order order) { log.info("订单取消事件: {}", order.getId()); // 1. 恢复库存 inventoryService.restoreStock(order); // 2. 处理退款 paymentService.refund(order); // 3. 发送取消通知 notificationService.orderCancelled(order); } }观察者模式 vs 发布-订阅模式
观察者模式 vs 发布-订阅模式
很多人将观察者模式和发布-订阅模式混为一谈,实际上两者有明显区别:
| 特性 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合程度 | 相对较强(主题需维护观察者列表) | 完全解耦(通过消息中介) |
| 通信方式 | 同步通信 | 支持异步通信 |
| 分层结构 | 两层(主题-观察者) | 三层(发布者-临时层-订阅者) |
| 消息过滤 | 不支持 | 支持(如topic) |
| 适用场景 | 同一应用中 | 分布式系统 |
观察者模式工作流程
观察者模式工作流程
观察者模式的工作流程主要分为以下几个步骤:
- 注册阶段:观察者调用subscrib方法将自己注册到主题
- 状态变更:主题对象的内部状态发生改变
- 通知阶段:主题调用notify方法通知所有注册的观察者
- 更新阶段:观察者收到通知后,调用对象的update方法更新自身状态
应用场景分析
应用场景
观察者模式广泛应用于以下场景:
1. GUI组件事件监听
// JavaFX 按钮点击监听 Button button = new Button("点击我"); button.setOnAction(event -> { System.out.println("按钮被点击!"); });2. 实时数据同步
// 数据库binlog监听(基于Canal) @Component public class DatabaseChangeHandler extends CanalClientEx { @EventListener public void handleChange(CanalEntry.EventType change) { // 同步到ES elasticsearchService.sync(change); // 同步到缓存 cacheService.sync(change); // 通知下游服务 messageService.publish(change); } }实现要点与注意事项
1. 观察者管理
// 使用线程安全的集合管理观察者 private final List<Observer> observers = Collections.synchronizedList(new ArrayList<>()); // 添加观察者 public void addObserver(Observer observer) { if (observer == null) { throw new IllegalArgumentException("观察者不能为空"); } synchronized(observers) { observers.add(observer); } } // 移除观察者 public void removeObserver(Observer observer) { observers.remove(observer); }2. 防止观察者循环引用
public class ConcreteSubject extends Observable { private boolean changed = false; @Override void notifyObservers() { // 使用锁避免循环引用 synchronized (this) { if (!changed) { return; } // 复制观察者列表避免并发修改 Observer[] arr = observers.toArray(new Observer[0]); clearChanged(); for (Observer observer : arr) { try { observer.update(this, null); } catch (Exception e) { log.warn("通知观察者失败:", e); } } } } }3. 异步通知避免阻塞
// 使用线程池异步通知 @Component public class AsyncEventPublisher { private final ExecutorService executor = Executors.newCachedThreadPool(); public void publishEventAsync(InventoryEvent event) { executor.execute(() -> { try { notifyObservers(event); } catch (Exception e) { log.error("异步事件通知失败", e); } }); } // 优雅关闭 @PreDestroy public void shutdown() { executor.shutdown(); } }4. 优先级排序
// 支持优先级的事件监听器 public class PriorityEventRegistry { private final PriorityQueue<EventListener> listeners = new PriorityQueue<>(Comparator.comparing(EventListener::getPriority)); public void registerListener(EventListener listener) { listeners.add(listener); } }优缺点分析
✅优点:
- 降低耦合度:发布者和订阅者相互解耦,易于系统扩展
- 遵守开闭原则:新增观察者时无需修改主题代码
- 实现广播通信:一个主题状态变化,多个观察者同步更新
- 消息传递高效:直接方法调用,性能优于消息队列
❌缺点:
- 观察者数量与性能相关:当观察者数量较多时,通知过程变慢
- 循环依赖风险:观察者和主题之间可能产生循环引用
- 同步通知阻塞所有观察者:一个观察者阻塞会影响整个通知链
观察者模式作为最经典的设计模式之一,它的思想贯穿于各种事件处理和通知机制中。从简单的对象监听,到复杂的分布式事件系统,观察者模式都能发挥其解耦和扩展性的优势。掌握观察者模式,能够帮助我们构建更加灵活、可维护的软件架构。