观察者模式:让对象之间保持“默契”
观察者模式,说白了就是解决“一对多”的依赖关系。当一个对象状态变了,其他依赖它的对象能自动收到通知。这就像你关注了一个公众号,每次更新你都能收到推送——你不用天天去刷,它主动告诉你。
我在项目中见过太多人把观察者模式用成了“广播站”,结果系统耦合得一塌糊涂。其实这个模式的核心就两点:被观察者(Subject)和观察者(Observer)。被观察者维护一个观察者列表,状态变化时挨个通知。就这么简单。
观察者模式的结构
先看类图,我习惯把观察者模式拆成四个角色:
结构其实很清晰:
- 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