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.Observable和java.util.Observer。不过我不太推荐直接用,因为Observable是一个类而不是接口,限制了灵活性。
更常见的是用事件监听机制。比如Spring中的ApplicationEvent和ApplicationListener,本质上就是观察者模式。
// 自定义事件
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帮我们把观察者的注册、通知都封装好了。我们只需要定义事件和监听器就行。
观察者模式的结构图
下面这张图展示了观察者模式的核心结构。我习惯用这种图来跟团队沟通设计思路。
避坑指南
我曾经踩过这些坑:
- 观察者执行顺序不可控:多个观察者之间如果有依赖关系,不要指望通知顺序。我建议每个观察者独立处理,互不依赖。
- 循环通知导致栈溢出:观察者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