观察者模式:设计与实践
一、什么是观察者模式
1. 基本定义
观察者模式(Observer Pattern)是一种行为型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
该模式通过分离“被观察者”(主题)和“观察者”,使观察者无需紧密耦合于被观察者,就能感知其状态变化并做出响应。核心是建立一种“发布-订阅”机制,实现对象间的松耦合通信。
2. 核心思想
观察者模式的核心在于解耦通知与响应。当系统中存在一个对象(主题)的状态变化需要触发多个其他对象(观察者)的响应操作时,通过观察者模式可让主题维护一个观察者列表,状态变化时主动通知所有观察者,而观察者无需知道其他观察者的存在,也无需修改主题的代码即可扩展新的响应逻辑。
二、观察者模式的特点
1. 一对多依赖
一个主题可以对应多个观察者,形成一对多的依赖关系,主题状态变化时所有观察者都会收到通知。
2. 松耦合
主题与观察者通过接口交互,彼此无需了解具体实现,一方的修改不会影响另一方。
3. 主动通知
主题状态变化时主动向观察者发送通知,无需观察者主动查询,提高响应效率。
4. 动态可扩展
可以在运行时动态添加或移除观察者,灵活扩展响应逻辑,符合开闭原则。
5. 触发链可能存在
多个观察者之间可能形成触发链,一个观察者的响应可能成为另一个主题的状态变化,需注意循环依赖问题。
| 特点 | 说明 |
|---|---|
| 一对多依赖 | 一个主题对应多个观察者,状态变化同步通知所有观察者 |
| 松耦合 | 主题与观察者通过接口交互,彼此独立演化 |
| 主动通知 | 主题主动推送状态变化,无需观察者轮询 |
| 动态可扩展 | 运行时可动态添加/移除观察者,扩展灵活 |
| 可能存在触发链 | 观察者的响应可能引发新的状态变化,需避免循环依赖 |
三、观察者模式的标准代码实现
1. 模式结构
观察者模式包含四个核心角色:
- 主题(Subject):被观察的对象,维护观察者列表,提供注册、移除和通知观察者的方法。
- 观察者(Observer):定义接收通知的接口,包含状态更新方法。
- 具体主题(ConcreteSubject):实现主题接口,存储自身状态,状态变化时通知观察者。
- 具体观察者(ConcreteObserver):实现观察者接口,定义收到通知后的具体响应逻辑,持有对具体主题的引用。
2. 代码实现示例
2.1 主题接口
import java.util.List;
/**
* 主题接口
* 定义观察者管理和通知方法
*/
public interface Subject {
/**
* 注册观察者
*/
void registerObserver(Observer observer);
/**
* 移除观察者
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
*/
void notifyObservers();
}
2.2 观察者接口
/**
* 观察者接口
* 定义接收通知的方法
*/
public interface Observer {
/**
* 接收主题通知并更新状态
* @param subject 主题对象
*/
void update(Subject subject);
}
2.3 具体主题
import java.util.ArrayList;
import java.util.List;
/**
* 具体主题
* 维护自身状态,实现通知逻辑
*/
public class ConcreteSubject implements Subject {
// 主题状态(示例:温度值)
private float temperature;
// 观察者列表
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() {
// 遍历所有观察者并通知
for (Observer observer : observers) {
observer.update(this);
}
}
/**
* 状态变更方法
* 状态更新后触发通知
*/
public void setTemperature(float temperature) {
this.temperature = temperature;
// 状态变化,通知观察者
notifyObservers();
}
/**
* 提供观察者获取状态的方法
*/
public float getTemperature() {
return temperature;
}
}
2.4 具体观察者
/**
* 具体观察者1:温度显示器
*/
public class TemperatureDisplay implements Observer {
// 持有主题引用,用于获取状态
private ConcreteSubject subject;
// 观察者自身状态
private float currentTemperature;
public TemperatureDisplay(ConcreteSubject subject) {
this.subject = subject;
// 注册为观察者
subject.registerObserver(this);
}
@Override
public void update(Subject subject) {
// 确认主题类型并获取最新状态
if (subject instanceof ConcreteSubject) {
this.currentTemperature = ((ConcreteSubject) subject).getTemperature();
display();
}
}
/**
* 显示温度(观察者的响应逻辑)
*/
private void display() {
System.out.println("当前温度:" + currentTemperature + "℃");
}
}
/**
* 具体观察者2:温度报警器
*/
public class TemperatureAlarm implements Observer {
private ConcreteSubject subject;
private float threshold = 30.0f; // 报警阈值
public TemperatureAlarm(ConcreteSubject subject) {
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void update(Subject subject) {
if (subject instanceof ConcreteSubject) {
float temp = ((ConcreteSubject) subject).getTemperature();
if (temp > threshold) {
alarm(temp);
}
}
}
/**
* 触发报警(观察者的响应逻辑)
*/
private void alarm(float temperature) {
System.out.println("【报警】温度过高:" + temperature + "℃,已超过阈值" + threshold + "℃");
}
}
2.5 客户端使用示例
/**
* 客户端
* 演示观察者模式的使用
*/
public class Client {
public static void main(String[] args) {
// 创建具体主题
ConcreteSubject thermostat = new ConcreteSubject();
// 创建观察者并自动注册
new TemperatureDisplay(thermostat);
new TemperatureAlarm(thermostat);
// 模拟温度变化,触发通知
System.out.println("=== 温度变为25℃ ===");
thermostat.setTemperature(25.0f);
System.out.println("\n=== 温度变为32℃ ===");
thermostat.setTemperature(32.0f);
System.out.println("\n=== 温度变为28℃ ===");
thermostat.setTemperature(28.0f);
}
}
3. 代码实现特点总结
| 角色 | 核心职责 | 代码特点 |
|---|---|---|
| 主题(Subject) | 定义观察者管理接口 | 声明register、remove、notify方法,维护观察者列表 |
| 观察者(Observer) | 定义接收通知的接口 | 声明update方法,参数通常包含主题引用 |
| 具体主题(ConcreteSubject) | 实现主题接口,管理状态 | 持有状态变量,状态变化时调用notifyObservers,提供状态获取方法 |
| 具体观察者(ConcreteObserver) | 实现观察者接口,定义响应逻辑 | 持有具体主题引用,update方法中获取主题状态并执行响应操作 |
四、支付框架设计中观察者模式的运用
以支付状态变更通知为例,说明观察者模式在支付系统中的具体实现:
1. 场景分析
支付系统中,订单支付状态变更(如“待支付→支付中”“支付中→支付成功”“支付中→支付失败”)需要触发一系列后续操作:
- 通知商户(调用商户回调接口)
- 更新订单状态(订单管理系统)
- 记录资金流水(资金结算系统)
- 发送用户通知(短信/APP推送)
- 触发风控审核(高金额订单支付成功后)
这些操作由不同模块负责,若硬编码在支付核心流程中,会导致模块耦合严重,新增操作需修改核心代码。使用观察者模式可将支付状态变更作为主题事件,各模块作为观察者订阅事件,实现灵活联动。
2. 设计实现
2.1 主题与观察者接口
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 支付状态主题接口
*/
public interface PaymentStatusSubject {
/**
* 注册观察者
*/
void registerObserver(PaymentStatusObserver observer);
/**
* 移除观察者
*/
void removeObserver(PaymentStatusObserver observer);
/**
* 通知观察者
*/
void notifyObservers(PaymentStatusEvent event);
}
/**
* 支付状态观察者接口
*/
public interface PaymentStatusObserver {
/**
* 接收支付状态变更通知
*/
void onStatusChanged(PaymentStatusEvent event);
}
2.2 支付状态事件
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 支付状态事件(包含状态变更信息)
*/
public class PaymentStatusEvent {
private String orderId; // 订单ID
private String oldStatus; // 旧状态
private String newStatus; // 新状态
private BigDecimal amount; // 支付金额
private LocalDateTime changeTime; // 状态变更时间
private String reason; // 状态变更原因(如支付成功、超时失败)
// 构造器
public PaymentStatusEvent(String orderId, String oldStatus, String newStatus,
BigDecimal amount, String reason) {
this.orderId = orderId;
this.oldStatus = oldStatus;
this.newStatus = newStatus;
this.amount = amount;
this.reason = reason;
this.changeTime = LocalDateTime.now();
}
// getter方法
public String getOrderId() { return orderId; }
public String getOldStatus() { return oldStatus; }
public String getNewStatus() { return newStatus; }
public BigDecimal getAmount() { return amount; }
public LocalDateTime getChangeTime() { return changeTime; }
public String getReason() { return reason; }
}
2.3 具体主题(支付状态服务)
/**
* 支付状态服务(具体主题)
*/
public class PaymentStatusService implements PaymentStatusSubject {
// 线程安全的观察者列表(支持并发操作)
private final List<PaymentStatusObserver> observers = new CopyOnWriteArrayList<>();
@Override
public void registerObserver(PaymentStatusObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(PaymentStatusObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(PaymentStatusEvent event) {
// 遍历所有观察者并通知
for (PaymentStatusObserver observer : observers) {
// 异步通知,避免阻塞支付主流程
new Thread(() -> observer.onStatusChanged(event)).start();
}
}
/**
* 变更支付状态(核心业务方法)
*/
public void changePaymentStatus(String orderId, String oldStatus, String newStatus,
BigDecimal amount, String reason) {
// 1. 更新自身状态(如数据库操作)
System.out.printf("订单%s状态变更:%s→%s,金额:%s%n",
orderId, oldStatus, newStatus, amount);
// 2. 创建事件并通知观察者
PaymentStatusEvent event = new PaymentStatusEvent(orderId, oldStatus, newStatus, amount, reason);
notifyObservers(event);
}
}
2.4 具体观察者实现
/**
* 商户通知观察者
*/
public class MerchantNotificationObserver implements PaymentStatusObserver {
@Override
public void onStatusChanged(PaymentStatusEvent event) {
// 仅关注支付成功状态
if ("SUCCESS".equals(event.getNewStatus())) {
String callbackUrl = getMerchantCallbackUrl(event.getOrderId());
System.out.printf("向商户回调:订单%s,状态%s,回调地址:%s%n",
event.getOrderId(), event.getNewStatus(), callbackUrl);
// 实际实现中会调用HTTP接口
}
}
// 获取商户回调地址(模拟)
private String getMerchantCallbackUrl(String orderId) {
return "https://merchant.example.com/callback?orderId=" + orderId;
}
}
/**
* 资金流水观察者
*/
public class FundFlowObserver implements PaymentStatusObserver {
@Override
public void onStatusChanged(PaymentStatusEvent event) {
System.out.printf("记录资金流水:订单%s,状态%s,金额%s,时间%s%n",
event.getOrderId(), event.getNewStatus(), event.getAmount(), event.getChangeTime());
// 实际实现中会写入资金流水表
}
}
/**
* 用户通知观察者
*/
public class UserNotificationObserver implements PaymentStatusObserver {
@Override
public void onStatusChanged(PaymentStatusEvent event) {
String userId = getUserIdByOrderId(event.getOrderId());
String message = "您的订单" + event.getOrderId() + "支付" +
("SUCCESS".equals(event.getNewStatus()) ? "成功" : "失败");
System.out.printf("向用户%s发送通知:%s%n", userId, message);
// 实际实现中会调用短信或推送服务
}
// 根据订单ID获取用户ID(模拟)
private String getUserIdByOrderId(String orderId) {
return "USER_" + orderId.substring(0, 8);
}
}
/**
* 风控审核观察者(仅关注大额支付)
*/
public class RiskAuditObserver implements PaymentStatusObserver {
private static final BigDecimal HIGH_AMOUNT_THRESHOLD = new BigDecimal("10000"); // 1万元
@Override
public void onStatusChanged(PaymentStatusEvent event) {
// 仅对支付成功且金额超过阈值的订单触发审核
if ("SUCCESS".equals(event.getNewStatus()) &&
event.getAmount().compareTo(HIGH_AMOUNT_THRESHOLD) > 0) {
System.out.printf("触发风控审核:订单%s,金额%s(超过%s元)%n",
event.getOrderId(), event.getAmount(), HIGH_AMOUNT_THRESHOLD);
// 实际实现中会创建风控工单
}
}
}
2.5 客户端使用示例
import java.math.BigDecimal;
/**
* 支付服务客户端
*/
public class PaymentServiceClient {
public static void main(String[] args) {
// 1. 创建支付状态主题
PaymentStatusSubject paymentStatusService = new PaymentStatusService();
// 2. 注册观察者
paymentStatusService.registerObserver(new MerchantNotificationObserver());
paymentStatusService.registerObserver(new FundFlowObserver());
paymentStatusService.registerObserver(new UserNotificationObserver());
paymentStatusService.registerObserver(new RiskAuditObserver());
// 3. 模拟支付状态变更
System.out.println("=== 场景1:支付成功 ===");
paymentStatusService.changePaymentStatus(
"ORDER_123456", "PAYING", "SUCCESS",
new BigDecimal("15000.00"), "用户支付成功");
System.out.println("\n=== 场景2:支付失败 ===");
paymentStatusService.changePaymentStatus(
"ORDER_789012", "PAYING", "FAILED",
new BigDecimal("200.00"), "银行卡余额不足");
}
}
3. 模式价值体现
- 模块解耦:支付核心服务与后续操作模块完全分离,支付流程无需知道具体有哪些观察者,新增操作只需添加观察者实现
- 灵活扩展:可根据业务需求动态注册/移除观察者,如某些商户关闭回调通知时,只需移除对应的观察者
- 异步处理:观察者的响应操作通过异步线程执行,不阻塞支付主流程,提高系统吞吐量
- 职责单一:每个观察者专注于单一职责(如商户通知、资金记录),便于维护和测试
- 一致性保证:所有观察者基于同一状态事件进行处理,确保数据一致性
五、开源框架中观察者模式的运用
以Spring Framework的事件机制为例,说明观察者模式在开源框架中的典型应用:
1. 核心实现分析
Spring的事件机制基于观察者模式,通过ApplicationEvent(事件)、ApplicationListener(观察者)和ApplicationEventPublisher(主题)实现对象间的解耦通信。
1.1 主题与事件定义
Spring的ApplicationEventPublisher接口扮演主题角色,提供发布事件的方法:
public interface ApplicationEventPublisher {
// 发布事件(通知观察者)
void publishEvent(ApplicationEvent event);
}
ApplicationEvent是所有事件的基类,包含事件源和时间戳:
public abstract class ApplicationEvent extends EventObject {
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
1.2 观察者接口
ApplicationListener作为观察者接口,定义事件处理方法:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 处理事件(观察者的响应逻辑)
void onApplicationEvent(E event);
}
1.3 具体实现与使用
Spring容器(如ApplicationContext)实现了ApplicationEventPublisher接口,作为具体主题:
// 1. 定义自定义事件
public class PaymentSuccessEvent extends ApplicationEvent {
private String orderId;
private BigDecimal amount;
public PaymentSuccessEvent(Object source, String orderId, BigDecimal amount) {
super(source);
this.orderId = orderId;
this.amount = amount;
}
// getter方法
}
// 2. 定义观察者
@Component
public class PaymentListener implements ApplicationListener<PaymentSuccessEvent> {
@Override
public void onApplicationEvent(PaymentSuccessEvent event) {
System.out.printf("处理支付成功事件:订单%s,金额%s%n",
event.getOrderId(), event.getAmount());
}
}
// 3. 发布事件
@Service
public class PaymentService {
@Autowired
private ApplicationEventPublisher publisher;
public void completePayment(String orderId, BigDecimal amount) {
// 业务处理...
// 发布事件
publisher.publishEvent(new PaymentSuccessEvent(this, orderId, amount));
}
}
2. 观察者模式在Spring中的价值
- 框架解耦:Spring内部组件(如容器初始化、上下文刷新)通过事件机制通信,降低耦合
- 扩展便捷:开发者可通过自定义事件和监听器扩展框架功能,无需修改Spring源码
- 异步支持:通过
@Async注解可实现观察者的异步处理,不阻塞事件发布者 - 类型安全:基于泛型的事件监听,确保观察者只处理感兴趣的事件类型
六、总结
1. 观察者模式的适用场景
- 当一个对象的状态变化需要触发多个其他对象的响应操作时
- 当需要解耦事件发布者和订阅者,使两者可独立演化时
- 当需要动态添加或移除响应逻辑,且不影响事件源时
- 当多个对象需要基于同一事件源的状态进行协同工作时
2. 观察者模式与其他模式的区别
- 与中介者模式:两者都处理对象间通信,但中介者模式通过中介者集中管理交互,观察者模式是主题直接通知观察者,前者适用于多对多复杂交互,后者适用于一对多的简单通知
- 与发布-订阅模式:发布-订阅模式是观察者模式的一种变体,前者通过消息队列等第三方组件解耦,观察者模式通常是直接引用
- 与责任链模式:责任链模式中观察者按顺序处理事件且可能中断传递,观察者模式中所有观察者都会收到通知并独立处理
3. 支付系统中的实践价值
- 业务扩展灵活:新增支付后的业务操作(如积分发放、优惠券激活)无需修改支付核心代码
- 系统韧性提升:单个观察者的故障不会影响其他观察者和支付主流程
- 开发效率提高:各团队可并行开发不同的观察者,减少协作成本
- 运维成本降低:可通过配置中心动态启用/禁用观察者,快速响应业务变化
- 问题排查便捷:每个观察者的日志独立,便于定位问题
4. 实践建议
- 明确事件粒度:事件应包含足够信息,避免观察者反向查询事件源
- 处理异步问题:核心操作建议同步执行,非核心操作异步执行,注意事务一致性
- 避免循环依赖:防止观察者的响应操作触发新的事件,形成无限循环
- 支持事件过滤:观察者可基于事件内容决定是否处理,提高效率
- 考虑事件持久化:关键业务事件建议持久化,支持重试和追溯
观察者模式通过“发布-订阅”机制实现了对象间的松耦合通信,在支付系统的状态变更、通知联动等场景中具有不可替代的价值。它不仅是一种设计模式,更是一种“模块化协同”的架构思想,合理应用可显著提升系统的灵活性和可维护性。