19、观察者模式:发布-订阅、事件监听机制

观察者模式,说白了就是「我盯着你,你变了我就知道」。

我在项目中遇到过不少这样的场景:用户下单后,需要同时发短信、发邮件、更新库存、记录日志。如果把这些逻辑全写在订单服务里,那代码就成了一锅粥。观察者模式就是用来解耦这种「一对多」依赖关系的。

什么是观察者模式?

观察者模式定义了一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

你想想看,这就像你关注了一个公众号。公众号发布文章,你就能收到推送。你不用天天去刷它,它变了会主动告诉你。这就是观察者模式的核心思想。

核心角色:

  • Subject(主题/被观察者):维护观察者列表,提供注册、删除、通知方法
  • Observer(观察者):定义更新接口,接收主题的通知
  • ConcreteSubject(具体主题):状态变化时通知所有观察者
  • ConcreteObserver(具体观察者):实现更新接口,处理通知

发布-订阅 vs 观察者模式

很多人把这两个混为一谈。其实它们有细微差别。

观察者模式是「直接通知」——主题直接调用观察者的方法。发布-订阅模式中间多了一个「事件通道」或「消息代理」,发布者和订阅者互不知道对方存在。

我个人习惯这样理解:观察者模式是「面对面聊天」,发布-订阅是「通过微信群聊」。后者耦合度更低,但实现也更复杂一些。

特性 观察者模式 发布-订阅模式
耦合度 主题知道观察者 完全解耦
通信方式 直接调用 通过消息代理
实现复杂度 简单 较复杂
典型应用 GUI事件监听 消息队列、事件总线

代码示例:从零实现观察者模式

我建议先看一个最朴素的实现,理解核心逻辑。

// 观察者接口
public interface Observer {
    void update(String message);
}

// 具体观察者:短信服务
public class SmsObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("发送短信:" + message);
    }
}

// 具体观察者:邮件服务
public class EmailObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("发送邮件:" + message);
    }
}

// 主题接口
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

// 具体主题:订单服务
public class OrderSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }

    // 业务方法
    public void createOrder(String orderId) {
        System.out.println("订单创建成功:" + orderId);
        notifyObservers("订单 " + orderId + " 已创建");
    }
}

// 使用示例
public class Demo {
    public static void main(String[] args) {
        OrderSubject orderSubject = new OrderSubject();

        // 注册观察者
        orderSubject.registerObserver(new SmsObserver());
        orderSubject.registerObserver(new EmailObserver());

        // 创建订单,自动通知
        orderSubject.createOrder("2024001");
    }
}

运行结果:

订单创建成功:2024001
发送短信:订单 2024001 已创建
发送邮件:订单 2024001 已创建

小提示:实际项目中,观察者的注册通常通过Spring的@EventListener注解或手动注入完成。不要在业务代码里硬编码new观察者。

事件监听机制:Java自带的观察者

Java其实已经内置了观察者模式的实现——java.util.Observablejava.util.Observer。不过我不太推荐直接用,因为Observable是一个类而不是接口,限制了灵活性。

更常见的是用事件监听机制。比如Spring中的ApplicationEventApplicationListener,本质上就是观察者模式。

// 自定义事件
public class OrderEvent extends ApplicationEvent {
    private String orderId;

    public OrderEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

// 事件监听器
@Component
public class SmsListener implements ApplicationListener<OrderEvent> {
    @Override
    public void onApplicationEvent(OrderEvent event) {
        System.out.println("短信监听器收到订单:" + event.getOrderId());
    }
}

// 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void createOrder(String orderId) {
        // 业务逻辑...
        publisher.publishEvent(new OrderEvent(this, orderId));
    }
}

你看,Spring帮我们把观察者的注册、通知都封装好了。我们只需要定义事件和监听器就行。

观察者模式的结构图

下面这张图展示了观察者模式的核心结构。我习惯用这种图来跟团队沟通设计思路。

<<接口>> Subject + registerObserver() + removeObserver() ConcreteSubject - observers: List + notifyObservers() <<接口>> Observer + update() ConcreteObserverA + update() ConcreteObserverB + update() 通知 观察者模式结构图 Subject 维护观察者列表,状态变化时遍历通知 接口 具体类 关联/通知

避坑指南

我曾经踩过这些坑:

  • 观察者执行顺序不可控:多个观察者之间如果有依赖关系,不要指望通知顺序。我建议每个观察者独立处理,互不依赖。
  • 循环通知导致栈溢出:观察者A更新时又触发了主题变化,主题又通知观察者B,B又反过来影响A。这种循环引用要命。我习惯在通知方法里加一个「正在更新」的标志位来防止重入。
  • 忘记移除观察者:特别是在Web应用中,每次请求都注册观察者但不移除,会导致内存泄漏。嗯,这里要注意,最好用弱引用或者在合适的时机清理。
  • 通知太频繁:如果状态变化很频繁,每次变化都通知所有观察者,性能会出问题。我建议做「防抖」或「批量通知」处理。

实际应用场景

观察者模式在Java生态中随处可见。我列几个常见的:

  • GUI事件处理:按钮点击、鼠标移动,都是观察者模式。你注册一个监听器,事件发生时框架通知你。
  • Spring事件机制:ApplicationEvent + ApplicationListener,解耦业务逻辑。
  • 消息队列:RabbitMQ、Kafka本质上就是发布-订阅模式,是观察者模式的升级版。
  • 响应式编程:RxJava、WebFlux中的Observable/Flowable,也是观察者模式的变体。
  • 日志框架:Log4j2的Appender,日志事件发生后通知各个Appender去处理。

我的建议:如果你发现代码中频繁出现「当A发生时,需要调用B、C、D的方法」,而且B、C、D还在不断新增,那就该用观察者模式了。它能让你的代码从「硬编码调用」变成「优雅的通知」。

观察者模式说白了就是「我变了,告诉你们一声」。它不复杂,但用好了能让代码的扩展性提升一个档次。你想想看,如果不用观察者模式,每次新增一个通知渠道都要改核心业务代码,那多痛苦。


公众号:蓝海资料掘金营,微信deep3321