观察者模式:从博客订阅到消息队列的解耦实践

观察者模式:从博客订阅到消息队列的解耦实践

一、模式核心:用事件驱动实现对象间松耦合

在新闻 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. 处理循环依赖(观察者反向修改主题)

  • 当观察者更新时可能再次触发主题状态变化,需增加

    事件防火墙

    java 复制代码
    public void update(Object data) {
        if (isProcessingEvent) return; // 避免循环触发
        isProcessingEvent = true;
        // 业务逻辑
        isProcessingEvent = false;
    }

4. 反模式:滥用观察者导致复杂度上升

  • 当观察者链过深(如 A→B→C→D)时,改用责任链模式事件总线重构
  • 避免为简单的双向通信使用观察者(直接调用接口更高效)

六、总结:何时该用观察者模式?

适用场景 核心特征 典型案例
实时数据通知 一个对象状态变化需触发多个对象的响应 股票行情推送、邮件订阅系统
模块解耦需求 对象间存在依赖但不想硬编码关联 微服务事件驱动架构、GUI 事件处理
异步事件处理 需要将通知与业务逻辑分离 消息队列、日志异步写入

观察者模式通过「主题抽象 + 事件驱动」的设计,将对象间的依赖关系从「直接调用」转化为「事件订阅」,这是实现松耦合系统的核心模式之一。下一篇我们将深入探讨状态模式,解析有限状态机在电商订单系统中的设计与实现,敬请期待!

扩展思考:观察者模式 vs 发布 - 订阅模式

两者核心思想相似,但存在关键区别:

模式 中介者 通知方式 应用场景
观察者模式 主题直接关联观察者 主题主动通知观察者 本地模块间通信
发布 - 订阅模式 事件总线作为中介 发布者与订阅者无直接关联 分布式系统、微服务间通信

理解这些差异,有助于在不同架构场景中选择最合适的设计方案。

相关推荐
这里有鱼汤11 分钟前
别傻了,这些量化策略AI 10 秒就能帮你写好
后端·python
Victor3562 小时前
Redis(16)Redis的有序集合(Sorted Set)类型有哪些常用命令?
后端
Victor3562 小时前
Redis(17)如何在Redis中设置键的过期时间?
后端
你的人类朋友10 小时前
说说git的变基
前端·git·后端
阿杆10 小时前
玩转 Amazon ElastiCache 免费套餐:小白也能上手
后端
阿杆10 小时前
无服务器每日自动推送 B 站热门视频
后端
公众号_醉鱼Java12 小时前
Elasticsearch 字段膨胀使用 Flattened类型
后端·掘金·金石计划
JohnYan12 小时前
工作笔记 - CentOS7环境运行Bun应用
javascript·后端·容器
探索java13 小时前
Netty Channel详解:从原理到实践
java·后端·netty
追逐时光者13 小时前
2025 年全面的 C#/.NET/.NET Core 学习路线集合,学习不迷路!
后端·.net