观察者模式:设计与实践

35 阅读13分钟

观察者模式:设计与实践

一、什么是观察者模式

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. 实践建议

  • 明确事件粒度:事件应包含足够信息,避免观察者反向查询事件源
  • 处理异步问题:核心操作建议同步执行,非核心操作异步执行,注意事务一致性
  • 避免循环依赖:防止观察者的响应操作触发新的事件,形成无限循环
  • 支持事件过滤:观察者可基于事件内容决定是否处理,提高效率
  • 考虑事件持久化:关键业务事件建议持久化,支持重试和追溯

观察者模式通过“发布-订阅”机制实现了对象间的松耦合通信,在支付系统的状态变更、通知联动等场景中具有不可替代的价值。它不仅是一种设计模式,更是一种“模块化协同”的架构思想,合理应用可显著提升系统的灵活性和可维护性。