引言
你一定遇到过这样的需求:用户注册成功后,既要"发欢迎邮件",又要"送新人优惠券",还要"记录注册日志",甚至以后可能还要"发短信通知"。如果把这些逻辑全部硬塞在注册方法里:
java
public void register(User user) {
// 保存用户
saveUser(user);
// 发邮件
sendEmail(user);
// 送券
sendCoupon(user);
// 记日志
writeLog(user);
// 以后加需求还得回来改这里......
}
每加一个后续动作,都要回来改这个方法,注册逻辑和一堆"副作用"死死耦合在一起。观察者模式(Observer Pattern) 就是来解决"一个对象状态变化,需要自动通知一群其他对象"这类问题的。它属于行为型设计模式,也是"发布-订阅"机制的鼻祖。
一、什么是观察者模式
观察者模式的核心思想:**定义对象间的一种一对多依赖关系,当一个对象(主题/被观察者)状态改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。**
两个核心角色:
- 主题(Subject / Observable):被观察的对象,维护一个观察者列表,提供注册、移除、通知的方法。
- 观察者(Observer):依赖主题的对象,当主题状态变化时收到通知并作出响应。
打个比方:你关注了一个公众号(订阅),公众号一发文,你和所有其他关注者都会收到推送。公众号是"主题",你们是"观察者"。这就是观察者模式,也就是常说的"发布-订阅"。
二、自定义实现观察者模式
用一个"气象站"的经典例子:气象站(主题)实时更新气温,多个显示面板(观察者)收到通知后各自展示。
2.1 定义观察者接口
java
public interface Observer {
void update(float temperature);
}
2.2 定义主题接口
java
public interface Subject {
void registerObserver(Observer observer); // 注册观察者
void removeObserver(Observer observer); // 移除观察者
void notifyObservers(); // 通知所有观察者
}
2.3 实现具体主题(气象站)
java
import java.util.ArrayList;
import java.util.List;
public class WeatherStation implements Subject {
private final List<Observer> observers = new ArrayList<>();
private float temperature;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature); // 把最新数据推给每个观察者
}
}
// 气温变化时,更新数据并通知所有观察者
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
}
2.4 实现具体观察者
java
public class PhoneDisplay implements Observer {
private final String name;
public PhoneDisplay(String name) {
this.name = name;
}
@Override
public void update(float temperature) {
System.out.println(name + " 显示当前气温:" + temperature + "℃");
}
}
2.5 使用
java
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
// 两个面板订阅了气象站
station.registerObserver(new PhoneDisplay("手机面板"));
station.registerObserver(new PhoneDisplay("电脑面板"));
// 气温变化,两个面板自动收到通知
station.setTemperature(25.5f);
// 手机面板 显示当前气温:25.5℃
// 电脑面板 显示当前气温:25.5℃
}
}
好处 :以后要新增一个"电视面板"观察者,只需 registerObserver 一个新观察者,完全不改气象站和其他观察者的代码,符合开闭原则。
三、观察者模式的两种通知方式
-
**推模型(Push)**:主题主动把所有数据"推"给观察者(上面例子就是)。优点是观察者简单,缺点是主题把所有数据都推过去,观察者可能只用到其中一部分。
-
**拉模型(Pull)**:主题只通知"我变了",观察者自己按需"拉"取关心的数据。更灵活,但观察者要持有主题的引用。
java
// 拉模型:update 不带数据,观察者自己来取
public interface Observer {
void update();
}
// 观察者里:
@Override
public void update() {
float temp = weatherStation.getTemperature(); // 自己拉数据
}
实际开发中两种都有,按需选择。
四、实际应用:Spring 事件机制
Spring 提供了一套完整的观察者模式实现------事件监听机制 ,ApplicationEvent + ApplicationListener + @EventListener。回到引言里的"注册后发邮件、送券、记日志",用 Spring 事件可以非常优雅地解耦:
4.1 定义事件
java
public class UserRegisterEvent extends ApplicationEvent {
private final User user;
public UserRegisterEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
4.2 发布事件
java
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher publisher;
public void register(User user) {
saveUser(user); // 核心逻辑
// 只发布事件,不关心谁在监听
publisher.publishEvent(new UserRegisterEvent(this, user));
}
}
4.3 多个监听器(观察者)各自处理
java
@Component
public class EmailListener {
@EventListener
public void onRegister(UserRegisterEvent event) {
System.out.println("给 " + event.getUser().getName() + " 发送欢迎邮件");
}
}
@Component
public class CouponListener {
@EventListener
public void onRegister(UserRegisterEvent event) {
System.out.println("给 " + event.getUser().getName() + " 发放新人优惠券");
}
}
@Component
public class LogListener {
@EventListener
public void onRegister(UserRegisterEvent event) {
System.out.println("记录注册日志:" + event.getUser().getName());
}
}
现在 register 方法干干净净只做核心业务,发邮件、送券、记日志这些"副作用"分散到了各自的监听器里,新增动作只需加一个监听器,彻底解耦。**这就是观察者模式在 Spring 里的最佳实践。**
另外,结合
@Async还能让这些监听器异步执行,进一步提升性能(这点在你前面写过的 AOP、统一处理那几篇里也提到过)。
五、观察者模式 vs 发布-订阅模式
很多人把这两个当成一回事,严格来说有细微差别:
| 对比项 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合方式 | 主题直接持有观察者,二者互相知道对方 | 通过"事件中心/消息中间件"中转,双方互不知道 |
| 通知机制 | 同步直接调用 | 通常异步、经消息队列 |
| 典型实现 | Java Observable、Spring 事件 |
MQ(RocketMQ、Kafka)、Redis 发布订阅 |
简单理解:**观察者模式是"点对点"的松耦合,发布-订阅是经过"中间人"的更彻底解耦。** 观察者模式可以看作发布-订阅在"单应用内"的简化版。
总结
观察者模式是实现"一对多自动通知、模块解耦"的利器:
- 核心:主题维护观察者列表,状态变化时逐一通知;观察者自动响应。
- 两种方式:推模型(主题推数据)和拉模型(观察者自己取)。
- Spring 事件机制 是观察者模式的最佳实践------
ApplicationEvent+@EventListener,把核心业务和"邮件、优惠券、日志"等副作用彻底解耦,新增功能只加监听器不动主流程。 - 与发布-订阅的区别:观察者是单应用内的直接通知,发布-订阅经过消息中间件中转、更彻底解耦。
一句话总结:当一个对象变化需要带动一群对象响应时,别把逻辑都堆在变化的方法里,用观察者模式把"谁响应、怎么响应"分离出去。