观察者模式
观察者模式用于对象之间的一对多通知。当一个对象状态变化时,它不直接调用所有后续逻辑,而是发布事件,让多个观察者各自处理。
为什么需要观察者模式
用户注册成功后,可能要做很多事:
- 发送欢迎短信。
- 发放新人优惠券。
- 初始化用户积分。
- 记录注册日志。
- 发送运营通知。
如果都写在注册方法里:
java
public void register(User user) {
userMapper.insert(user);
smsService.sendWelcome(user);
couponService.sendNewUserCoupon(user);
pointService.initPoint(user);
logService.record(user);
}问题:
- 注册主流程越来越臃肿。
- 新增后续动作要改注册代码。
- 某个非核心动作失败可能影响注册。
- 主流程知道太多下游细节。
观察者模式把后续动作拆出去:
mermaid
flowchart TD
A["用户注册成功"] --> B["发布 UserRegisteredEvent"]
B --> C["短信监听器"]
B --> D["优惠券监听器"]
B --> E["积分监听器"]
B --> F["日志监听器"]基本结构
mermaid
flowchart TD
A["Subject 主题"] --> B["Observer A"]
A --> C["Observer B"]
A --> D["Observer C"]
A --> E["状态变化时 notify"]| 角色 | 作用 |
|---|---|
| Subject | 保存观察者,状态变化时通知 |
| Observer | 定义收到通知后的处理接口 |
| ConcreteObserver | 具体观察者,执行自己的逻辑 |
Java 简单 Demo
事件:
java
public class UserRegisteredEvent {
private final Long userId;
public UserRegisteredEvent(Long userId) {
this.userId = userId;
}
public Long getUserId() {
return userId;
}
}监听器接口:
java
public interface EventListener {
void onEvent(UserRegisteredEvent event);
}具体监听器:
java
public class SmsListener implements EventListener {
@Override
public void onEvent(UserRegisteredEvent event) {
System.out.println("send sms to user: " + event.getUserId());
}
}发布器:
java
public class EventPublisher {
private final List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void publish(UserRegisteredEvent event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}Spring 事件机制
Spring 中可以使用 ApplicationEventPublisher 发布进程内事件。
java
@Service
public class UserService {
private final ApplicationEventPublisher publisher;
public UserService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(Long userId) {
// 保存用户
publisher.publishEvent(new UserRegisteredEvent(userId));
}
}监听:
java
@Component
public class CouponListener {
@EventListener
public void handle(UserRegisteredEvent event) {
System.out.println("send coupon to user: " + event.getUserId());
}
}和消息队列的区别
| 对比项 | 观察者模式 | 消息队列 |
|---|---|---|
| 范围 | 进程内为主 | 跨进程、跨服务 |
| 可靠性 | 依赖应用进程 | Broker 可持久化和重试 |
| 性能 | 本地调用快 | 有网络和 Broker 成本 |
| 失败处理 | 要自己定义 | 通常有 ACK、重试、死信 |
| 场景 | 本地扩展点 | 分布式异步通信 |
如果用户注册后要通知另一个微服务,应该使用 消息队列,而不是进程内观察者。
风险和边界
| 风险 | 后果 | 建议 |
|---|---|---|
| 监听器太多 | 一次事件触发大量逻辑,难排查 | 只放明确相关的扩展动作 |
| 监听器失败 | 是否影响主流程不清楚 | 明确定义同步/异步和异常策略 |
| 监听器互相依赖 | 顺序耦合,失去解耦价值 | 观察者之间不要互相依赖 |
| 用本地事件替代 MQ | 服务重启或跨服务消息丢失 | 可靠事件用 MQ |
小结
观察者模式适合“主流程完成后,需要通知多个独立后续动作”的场景。它的价值是降低主流程和扩展逻辑的耦合,但它不是可靠消息系统。是否同步执行、失败是否影响主流程、是否需要重试,都要在设计时说清楚。
