观察者模式:让对象之间保持“默契”

观察者模式,说白了就是解决“一对多”的依赖关系。当一个对象状态变了,其他依赖它的对象能自动收到通知。这就像你关注了一个公众号,每次更新你都能收到推送——你不用天天去刷,它主动告诉你。

我在项目中见过太多人把观察者模式用成了“广播站”,结果系统耦合得一塌糊涂。其实这个模式的核心就两点:被观察者(Subject)观察者(Observer)。被观察者维护一个观察者列表,状态变化时挨个通知。就这么简单。

观察者模式的结构

先看类图,我习惯把观察者模式拆成四个角色:

«接口» Subject + attach(Observer) + detach(Observer) + notify() ConcreteSubject - observers: List - state: Object + getState() «接口» Observer + update() ConcreteObserver - subject: Subject - observerState: Object + update() 通知 持有引用

结构其实很清晰:

  • Subject:抽象主题,定义注册、移除、通知观察者的接口
  • ConcreteSubject:具体主题,持有状态,状态变化时通知所有观察者
  • Observer:抽象观察者,定义更新接口
  • ConcreteObserver:具体观察者,收到通知后执行自己的逻辑

关键点:观察者模式实现了松耦合。主题只知道观察者实现了某个接口,不知道具体是谁。反过来,观察者也可以随时加入或离开。

推模型 vs 拉模型

这里有个经典问题:通知的时候,到底该传什么数据?

推模型(Push):主题把变化的数据主动推给观察者。比如通知时直接把新状态传过去。

// 推模型:主题主动推送数据
public interface Observer {
    void update(String newState);  // 直接把数据传过来
}

public class ConcreteSubject {
    private String state;
    private List<Observer> observers = new ArrayList<>();
    
    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }
    
    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this.state);  // 推送数据
        }
    }
}

拉模型(Pull):主题只通知“我变了”,观察者自己来取数据。

// 拉模型:观察者自己来拉数据
public interface Observer {
    void update(Subject subject);  // 把主题传过去,自己取
}

public class ConcreteObserver implements Observer {
    private String observerState;
    
    @Override
    public void update(Subject subject) {
        // 自己从主题拉数据
        this.observerState = ((ConcreteSubject) subject).getState();
    }
}

我个人更倾向于推模型。为什么?

  • 推模型接口更简单,观察者不需要知道主题的内部结构
  • 拉模型容易破坏封装——观察者需要强制转型,还得知道主题的具体类型

避坑指南:我曾经在一个项目中用了拉模型,结果观察者里到处是 instanceof 和强制转型。后来需求变了,主题增加了新字段,所有观察者都得改——那叫一个酸爽。所以除非你有非常明确的理由(比如观察者只需要部分数据),否则优先用推模型。

观察者模式在 Spring 事件监听机制中的应用

Spring 的事件机制,其实就是观察者模式的经典实现。我刚开始用 Spring 时,看到 ApplicationEvent 和 ApplicationListener 这对组合,第一反应就是:这不就是观察者模式吗?

Spring 里三个核心角色:

  • 事件(Event):继承 ApplicationEvent,相当于被观察者的状态
  • 监听器(Listener):实现 ApplicationListener,相当于观察者
  • 事件发布器(Publisher):通过 ApplicationEventPublisher 发布事件,相当于主题

看个实际例子:

// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private Long orderId;
    private String userId;
    
    public OrderCreatedEvent(Object source, Long orderId, String userId) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
    }
    // getters...
}

// 2. 定义监听器
@Component
public class EmailNotificationListener implements ApplicationListener<OrderCreatedEvent> {
    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        System.out.println("发送邮件通知用户:" + event.getUserId() 
                         + ",订单:" + event.getOrderId());
    }
}

@Component
public class InventoryUpdateListener implements ApplicationListener<OrderCreatedEvent> {
    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        System.out.println("更新库存,订单:" + event.getOrderId());
    }
}

// 3. 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void createOrder(Long orderId, String userId) {
        // 业务逻辑...
        System.out.println("订单创建成功");
        
        // 发布事件
        publisher.publishEvent(new OrderCreatedEvent(this, orderId, userId));
    }
}

你看,OrderService 只管创建订单,发邮件、更新库存这些事完全交给监听器。哪天不想发邮件了,直接删掉 EmailNotificationListener 就行,OrderService 一行代码都不用改。

小技巧:Spring 4.2 之后,可以用 @EventListener 注解替代实现接口,更简洁:

@Component
public class EmailNotificationListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("发送邮件通知用户:" + event.getUserId());
    }
}

Spring 的事件机制默认是同步的。什么意思?就是发布事件的线程会阻塞,直到所有监听器执行完毕。如果你想要异步,加个 @Async 注解就行:

@Component
@EnableAsync
public class AsyncEmailNotificationListener {
    @Async
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 这个会在独立线程中执行
        System.out.println("异步发送邮件");
    }
}

注意:异步监听器要小心事务问题。我曾经遇到过一个坑:监听器里抛了异常,但主事务已经提交了,结果数据不一致。解决方案是:要么在监听器里自己处理异常,要么用事务同步事件(TransactionSynchronizationManager)。

总结一下

观察者模式的核心价值就一句话:让主题和观察者各自独立,又能保持同步。推模型简单直接,拉模型灵活但容易破坏封装——我建议你默认用推模型。

Spring 的事件机制把这个模式用到了极致。从业务解耦到异步处理,从事务同步到条件监听,Spring 把观察者模式玩出了花。你想想看,一个订单创建事件,可以同时触发发邮件、更新库存、记录日志、推送通知……这些逻辑如果写在一起,那代码得多恐怖?

嗯,观察者模式就是这么个好东西。下次你遇到“当A发生时,B、C、D都要做点什么”的场景,不妨试试它。


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