观察者模式:从理论到生产实践

观察者模式深度解析:从理论到生产实践

在软件开发中,我们经常需要实现"一个对象状态变化,多个对象自动更新"的场景。比如用户注册成功时,需要发送欢迎邮件、赠送积分、记录日志等多个操作。这种一对多的依赖关系,正是观察者模式的典型应用场景。

观察者模式是什么?

观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,所有观察者都会收到通知并自动更新。

这里借用建筑监理的比喻:投资方(观察者们)委托建筑师(主题)监督项目进度。每当有建筑进展,建筑师主动通知所有投资方,无需投资方反复询问。

核心角色与实现

观察者模式主要包含四个角色:

  1. Subject(主题):定义了被观察者对象的基本接口
  2. ConcreteSubject(具体主题):主题的具体实现,维护观察者列表
  3. Observer(观察者):定义了观察者对通知做出反应的方法
  4. ConcreteObserver(具体观察者):实现Observer接口,观察主题并执行相应操作

下面通过一个实际的电商库存管理示例来展示观察者模式的实现:

java 复制代码
// 观察者接口
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); // 库存积压场景
    }
}

运行结果:

diff 复制代码
添加观察者: InventoryAlert
添加观察者: OrderProcessor
添加观察者: PriceAdjuster

--- 模拟库存变化 ---
⚠️ 库存预警: Product-iphone 库存不足,当前数量: 3
📦 自动触发补货: Product-iphone 正在向供应商下单...

--- 模拟供应商补货 ---

--- 模拟库存积压 ---
💰 库存充足,可以考虑促销降价!

Spring框架中的事件机制

观察者模式在Spring框架中被广泛应用,其事件机制就是典型的实现。Spring的事件机制以ApplicationContext为核心,实现了观察者模式的高级封装:

java 复制代码
// 自定义事件
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本质上也是观察者模式的延伸,但采用了更灵活的发布-订阅模式:

java 复制代码
// 配置多队列消息监听
@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 发布-订阅模式

很多人将观察者模式和发布-订阅模式混为一谈,实际上两者有明显区别:

特性 观察者模式 发布-订阅模式
耦合程度 相对较强(主题需维护观察者列表) 完全解耦(通过消息中介)
通信方式 同步通信 支持异步通信
分层结构 两层(主题-观察者) 三层(发布者-临时层-订阅者)
消息过滤 不支持 支持(如topic)
适用场景 同一应用中 分布式系统

观察者模式工作流程

观察者模式的工作流程主要分为以下几个步骤:

  1. 注册阶段:观察者调用subscrib方法将自己注册到主题
  2. 状态变更:主题对象的内部状态发生改变
  3. 通知阶段:主题调用notify方法通知所有注册的观察者
  4. 更新阶段:观察者收到通知后,调用对象的update方法更新自身状态

应用场景分析

观察者模式广泛应用于以下场景:

1. GUI组件事件监听

java 复制代码
// JavaFX 按钮点击监听
Button button = new Button("点击我");
button.setOnAction(event -> {
    System.out.println("按钮被点击!");
});

2. 实时数据同步

java 复制代码
// 数据库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. 观察者管理

java 复制代码
// 使用线程安全的集合管理观察者
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. 防止观察者循环引用

java 复制代码
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. 异步通知避免阻塞

java 复制代码
// 使用线程池异步通知
@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();
    }
}

优缺点分析

优点:

  1. 降低耦合度:发布者和订阅者相互解耦,易于系统扩展
  2. 遵守开闭原则:新增观察者时无需修改主题代码
  3. 实现广播通信:一个主题状态变化,多个观察者同步更新
  4. 消息传递高效:直接方法调用,性能优于消息队列

缺点:

  1. 观察者数量与性能相关:当观察者数量较多时,通知过程变慢
  2. 循环依赖风险:观察者和主题之间可能产生循环引用
  3. 同步通知阻塞所有观察者:一个观察者阻塞会影响整个通知链

观察者模式作为最经典的设计模式之一,它的思想贯穿于各种事件处理和通知机制中。从简单的对象监听,到复杂的分布式事件系统,观察者模式都能发挥其解耦和扩展性的优势。掌握观察者模式,能够帮助我们构建更加灵活、可维护的软件架构。

相关推荐
北城以北88882 小时前
SpringBoot--Redis基础知识
java·spring boot·redis·后端·intellij-idea
wniuniu_2 小时前
ceph中的rbd的稀疏写入
java·服务器·数据库
2201_757830872 小时前
条件分页查询
java·开发语言
重生之我是Java开发战士2 小时前
【数据结构】Java对象的比较
java·jvm·数据结构
橘子132 小时前
Linux线程——一些概念(七)
java·redis·缓存
magic_kid_20102 小时前
IDEA 复制到 Windows 远程桌面失败的原因与解决方案
java·ide·intellij-idea
风月歌2 小时前
基于微信小程序的学习资料销售平台源代码(源码+文档+数据库)
java·数据库·mysql·微信小程序·小程序·毕业设计·源码
巴拉巴拉~~2 小时前
KMP 算法通用步进器组件:KmpStepperWidget 横向 / 纵向 + 匹配进度 + 全样式自定义
java·服务器·开发语言
贺今宵2 小时前
使用idea启动一个springboot项目
java·ide·intellij-idea