状态模式:设计与实践

25 阅读15分钟

状态模式:设计与实践

一、什么是状态模式

1. 基本定义

状态模式(State Pattern)是一种行为型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类

该模式通过将对象的每个状态封装为独立的状态类,使对象在不同状态下的行为由对应状态类负责,而非通过大量条件判断实现。核心是将“状态判断”与“行为实现”分离,当对象状态改变时,其行为会随之改变,就像对象的类发生了变化一样。

2. 核心思想

状态模式的核心在于状态封装与行为委派。当对象的行为取决于其状态,且状态会频繁变化时,通过将每个状态的行为封装为独立类,可消除代码中的条件分支(如if-elseswitch-case),使状态转换和状态行为的扩展更加灵活。这种设计既保证了状态变化的一致性,又使每种状态的行为易于维护和扩展。

二、状态模式的特点

1. 状态封装

每个状态被封装为独立的状态类,状态对应的行为在类内部实现,符合单一职责原则。

2. 行为委派

环境类(持有状态的对象)将行为委派给当前状态类,自身不包含状态相关的条件判断。

3. 状态转换显式化

状态之间的转换在状态类中明确定义,而非分散在环境类的条件判断中,便于跟踪状态流转。

4. 动态行为变化

环境类的行为会随着内部状态的改变而自动变化,无需修改环境类代码。

5. 可扩展性强

新增状态只需添加新的状态类,无需修改现有状态类和环境类,符合开闭原则。

特点说明
状态封装每个状态对应独立类,行为在类内实现
行为委派环境类将行为委派给当前状态类
状态转换显式化状态转换逻辑在状态类中明确声明
动态行为变化状态改变时,环境类行为自动变化
可扩展性强新增状态只需添加新类,无需修改原有代码

三、状态模式的标准代码实现

1. 模式结构

状态模式包含三个核心角色:

  • 状态接口(State):定义所有状态的公共行为接口,声明环境类可能触发的操作。
  • 具体状态类(ConcreteState):实现状态接口,包含该状态下的具体行为,以及状态转换逻辑。
  • 环境类(Context):持有当前状态的引用,提供设置状态的接口,将行为委派给当前状态类。

2. 代码实现示例

2.1 状态接口
/**
 * 状态接口
 * 定义所有状态的公共行为
 */
public interface State {
    /**
     * 处理操作1
     */
    void handleOperation1(Context context);

    /**
     * 处理操作2
     */
    void handleOperation2(Context context);
}
2.2 具体状态类
/**
 * 具体状态A
 */
public class ConcreteStateA implements State {
    // 单例模式(避免重复创建状态对象)
    private static final ConcreteStateA INSTANCE = new ConcreteStateA();
    private ConcreteStateA() {}
    public static ConcreteStateA getInstance() {
        return INSTANCE;
    }

    @Override
    public void handleOperation1(Context context) {
        System.out.println("状态A:执行操作1");
        // 操作1完成后,转换到状态B
        context.setState(ConcreteStateB.getInstance());
    }

    @Override
    public void handleOperation2(Context context) {
        System.out.println("状态A:执行操作2(仅状态A支持)");
        // 操作2在状态A下不改变状态
    }
}

/**
 * 具体状态B
 */
public class ConcreteStateB implements State {
    private static final ConcreteStateB INSTANCE = new ConcreteStateB();
    private ConcreteStateB() {}
    public static ConcreteStateB getInstance() {
        return INSTANCE;
    }

    @Override
    public void handleOperation1(Context context) {
        System.out.println("状态B:执行操作1(与状态A逻辑不同)");
        // 操作1在状态B下不改变状态
    }

    @Override
    public void handleOperation2(Context context) {
        System.out.println("状态B:执行操作2");
        // 操作2完成后,转换到状态A
        context.setState(ConcreteStateA.getInstance());
    }
}
2.3 环境类
/**
 * 环境类
 * 持有当前状态,委派行为给状态类
 */
public class Context {
    // 当前状态引用
    private State currentState;

    // 初始化状态为A
    public Context() {
        this.currentState = ConcreteStateA.getInstance();
    }

    // 设置新状态
    public void setState(State state) {
        this.currentState = state;
        System.out.println("状态已切换为:" + state.getClass().getSimpleName());
    }

    // 委托操作1给当前状态
    public void operation1() {
        currentState.handleOperation1(this);
    }

    // 委托操作2给当前状态
    public void operation2() {
        currentState.handleOperation2(this);
    }
}
2.4 客户端使用示例
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        Context context = new Context();

        System.out.println("=== 第一次调用操作1 ===");
        context.operation1(); // 状态A→执行操作1→切换到状态B

        System.out.println("\n=== 调用操作2 ===");
        context.operation2(); // 状态B→执行操作2→切换到状态A

        System.out.println("\n=== 第二次调用操作1 ===");
        context.operation1(); // 状态A→执行操作1→切换到状态B
    }
}

3. 代码实现特点总结

角色核心职责代码特点
状态接口(State)定义所有状态的公共行为声明状态相关的操作方法,为所有具体状态提供统一接口
具体状态类(ConcreteState)实现状态接口,包含状态行为和转换逻辑通常采用单例模式,handleXxx方法中实现当前状态的行为,并调用context.setState()实现转换
环境类(Context)持有当前状态,提供操作接口包含currentState字段和setState()方法,将操作委派给currentState的对应方法

四、支付框架设计中状态模式的运用

支付风控状态监控实现为例,说明状态模式在支付系统中的具体应用:

1. 场景分析

支付系统需要实时监控交易风险,根据风险评估结果动态调整支付流程,典型风控状态包括:

  • 正常状态(Normal):交易风险低,允许直接支付
  • 验证状态(Verification):存在中等风险,需要用户完成二次验证(如短信验证码)
  • 审核状态(Review):高风险交易,暂停支付流程,等待人工审核
  • 拒绝状态(Rejected):风险过高,直接拒绝支付

不同状态下的行为不同(如正常状态直接扣款,验证状态需先验证),且状态会根据用户操作(如完成验证)或审核结果转换(如审核通过→正常状态)。使用状态模式可将每种状态的行为和转换逻辑封装为独立类,避免风控逻辑中出现大量条件判断。

2. 设计实现

2.1 状态接口与环境类
import java.math.BigDecimal;

/**
 * 风控状态接口
 */
public interface RiskState {
    /**
     * 处理支付请求
     */
    void processPayment(RiskContext context, PaymentRequest request);

    /**
     * 处理用户验证(如短信验证码)
     */
    void handleVerification(RiskContext context, String verificationCode);

    /**
     * 处理人工审核结果
     */
    void handleReviewResult(RiskContext context, boolean approved, String reason);

    /**
     * 获取当前状态名称
     */
    String getStateName();
}

/**
 * 风控上下文(环境类)
 */
public class RiskContext {
    // 当前风控状态
    private RiskState currentState;
    // 交易信息(简化)
    private String orderId;
    private BigDecimal amount;
    private String userId;

    public RiskContext(String orderId, BigDecimal amount, String userId) {
        this.orderId = orderId;
        this.amount = amount;
        this.userId = userId;
        // 初始状态:默认正常状态(后续会根据风险评估调整)
        this.currentState = NormalRiskState.getInstance();
    }

    // 设置当前状态
    public void setState(RiskState state) {
        System.out.printf("订单%s:风控状态从[%s]切换到[%s]%n",
                orderId, currentState.getStateName(), state.getStateName());
        this.currentState = state;
    }

    // 委托方法:处理支付请求
    public void processPayment(PaymentRequest request) {
        currentState.processPayment(this, request);
    }

    // 委托方法:处理用户验证
    public void handleVerification(String verificationCode) {
        currentState.handleVerification(this, verificationCode);
    }

    // 委托方法:处理审核结果
    public void handleReviewResult(boolean approved, String reason) {
        currentState.handleReviewResult(this, approved, reason);
    }

    // getter方法
    public String getOrderId() { return orderId; }
    public BigDecimal getAmount() { return amount; }
    public String getUserId() { return userId; }
}
2.2 具体状态类实现
2.2.1 正常状态(Normal)
/**
 * 正常风控状态
 * 低风险交易,直接执行支付
 */
public class NormalRiskState implements RiskState {
    // 单例模式
    private static final NormalRiskState INSTANCE = new NormalRiskState();
    private NormalRiskState() {}
    public static NormalRiskState getInstance() {
        return INSTANCE;
    }

    @Override
    public void processPayment(RiskContext context, PaymentRequest request) {
        System.out.printf("订单%s:正常风控状态,执行支付流程%n", context.getOrderId());
        // 调用支付服务完成扣款
        PaymentService.processPayment(request);
        // 支付完成后,状态流转结束
    }

    @Override
    public void handleVerification(RiskContext context, String verificationCode) {
        // 正常状态下无需验证,忽略该操作
        System.out.printf("订单%s:正常状态无需验证,忽略操作%n", context.getOrderId());
    }

    @Override
    public void handleReviewResult(RiskContext context, boolean approved, String reason) {
        // 正常状态下无需审核,忽略该操作
        System.out.printf("订单%s:正常状态无需审核,忽略操作%n", context.getOrderId());
    }

    @Override
    public String getStateName() {
        return "Normal";
    }
}
2.2.2 验证状态(Verification)
/**
 * 验证风控状态
 * 中等风险,需用户完成二次验证
 */
public class VerificationRiskState implements RiskState {
    private static final VerificationRiskState INSTANCE = new VerificationRiskState();
    private VerificationRiskState() {}
    public static VerificationRiskState getInstance() {
        return INSTANCE;
    }

    @Override
    public void processPayment(RiskContext context, PaymentRequest request) {
        System.out.printf("订单%s:中等风险,需完成二次验证%n", context.getOrderId());
        // 发送验证码
        String verificationCode = SmsService.sendCode(context.getUserId());
        System.out.printf("订单%s:已发送验证码至用户手机,等待验证%n", context.getOrderId());
    }

    @Override
    public void handleVerification(RiskContext context, String verificationCode) {
        System.out.printf("订单%s:验证风控状态,校验验证码%s%n", context.getOrderId(), verificationCode);
        // 验证验证码有效性
        boolean valid = SmsService.verifyCode(context.getUserId(), verificationCode);
        if (valid) {
            System.out.printf("订单%s:验证通过,切换至正常状态%n", context.getOrderId());
            // 验证通过,切换到正常状态继续支付
            context.setState(NormalRiskState.getInstance());
            // 自动触发支付流程
            context.processPayment(new PaymentRequest(context.getOrderId(), context.getAmount()));
        } else {
            System.out.printf("订单%s:验证失败,切换至拒绝状态%n", context.getOrderId());
            // 验证失败,切换到拒绝状态
            context.setState(RejectedRiskState.getInstance());
        }
    }

    @Override
    public void handleReviewResult(RiskContext context, boolean approved, String reason) {
        // 验证状态下无需审核,忽略该操作
        System.out.printf("订单%s:验证状态无需审核,忽略操作%n", context.getOrderId());
    }

    @Override
    public String getStateName() {
        return "Verification";
    }
}
2.2.3 审核状态(Review)
/**
 * 审核风控状态
 * 高风险交易,等待人工审核
 */
public class ReviewRiskState implements RiskState {
    private static final ReviewRiskState INSTANCE = new ReviewRiskState();
    private ReviewRiskState() {}
    public static ReviewRiskState getInstance() {
        return INSTANCE;
    }

    @Override
    public void processPayment(RiskContext context, PaymentRequest request) {
        System.out.printf("订单%s:高风险交易,已暂停支付,等待人工审核%n", context.getOrderId());
        // 记录审核任务,通知风控人员
        AuditService.createAuditTask(context.getOrderId(), context.getAmount(), context.getUserId());
    }

    @Override
    public void handleVerification(RiskContext context, String verificationCode) {
        // 审核状态下验证无效,需等待审核结果
        System.out.printf("订单%s:审核状态不支持验证,需等待人工审核%n", context.getOrderId());
    }

    @Override
    public void handleReviewResult(RiskContext context, boolean approved, String reason) {
        System.out.printf("订单%s:处理审核结果,是否通过:%s,原因:%s%n",
                context.getOrderId(), approved, reason);
        if (approved) {
            // 审核通过,切换到正常状态继续支付
            context.setState(NormalRiskState.getInstance());
            context.processPayment(new PaymentRequest(context.getOrderId(), context.getAmount()));
        } else {
            // 审核不通过,切换到拒绝状态
            context.setState(RejectedRiskState.getInstance());
        }
    }

    @Override
    public String getStateName() {
        return "Review";
    }
}
2.2.4 拒绝状态(Rejected)
/**
 * 拒绝风控状态
 * 高风险交易,直接拒绝
 */
public class RejectedRiskState implements RiskState {
    private static final RejectedRiskState INSTANCE = new RejectedRiskState();
    private RejectedRiskState() {}
    public static RejectedRiskState getInstance() {
        return INSTANCE;
    }

    @Override
    public void processPayment(RiskContext context, PaymentRequest request) {
        System.out.printf("订单%s:高风险交易,已拒绝支付%n", context.getOrderId());
        // 记录拒绝原因,通知用户
        NotificationService.sendRejectNotice(context.getUserId(), context.getOrderId());
    }

    @Override
    public void handleVerification(RiskContext context, String verificationCode) {
        // 拒绝状态下验证无效
        System.out.printf("订单%s:已拒绝支付,验证无效%n", context.getOrderId());
    }

    @Override
    public void handleReviewResult(RiskContext context, boolean approved, String reason) {
        // 拒绝状态下审核结果无效
        System.out.printf("订单%s:已拒绝支付,审核结果无效%n", context.getOrderId());
    }

    @Override
    public String getStateName() {
        return "Rejected";
    }
}
2.3 辅助类与客户端使用
/**
 * 支付请求模型
 */
public class PaymentRequest {
    private String orderId;
    private BigDecimal amount;

    public PaymentRequest(String orderId, BigDecimal amount) {
        this.orderId = orderId;
        this.amount = amount;
    }

    // getter方法
    public String getOrderId() { return orderId; }
    public BigDecimal getAmount() { return amount; }
}

/**
 * 支付服务(模拟)
 */
class PaymentService {
    public static void processPayment(PaymentRequest request) {
        System.out.printf("订单%s:支付成功,金额:%s%n", request.getOrderId(), request.getAmount());
    }
}

/**
 * 风控服务(客户端)
 */
public class RiskControlService {
    /**
     * 评估风险并处理支付
     */
    public void evaluateAndProcess(PaymentRequest request, String userId) {
        // 1. 创建风控上下文
        RiskContext context = new RiskContext(request.getOrderId(), request.getAmount(), userId);

        // 2. 风险评估(模拟):根据金额和用户历史判断风险等级
        RiskLevel level = assessRiskLevel(request, userId);
        System.out.printf("订单%s:风险评估等级:%s%n", request.getOrderId(), level);

        // 3. 根据风险等级设置初始状态
        switch (level) {
            case LOW:
                context.setState(NormalRiskState.getInstance());
                break;
            case MEDIUM:
                context.setState(VerificationRiskState.getInstance());
                break;
            case HIGH:
                context.setState(ReviewRiskState.getInstance());
                break;
            case CRITICAL:
                context.setState(RejectedRiskState.getInstance());
                break;
        }

        // 4. 处理支付请求
        context.processPayment(request);

        // 5. 模拟用户操作或审核结果(实际中由事件触发)
        simulateUserAction(context, level);
    }

    // 模拟风险评估
    private RiskLevel assessRiskLevel(PaymentRequest request, String userId) {
        // 简化逻辑:金额大于10000视为高风险,5000-10000视为中风险
        if (request.getAmount().compareTo(new BigDecimal("10000")) > 0) {
            return RiskLevel.HIGH;
        } else if (request.getAmount().compareTo(new BigDecimal("5000")) > 0) {
            return RiskLevel.MEDIUM;
        } else {
            return RiskLevel.LOW;
        }
    }

    // 模拟用户操作(如完成验证、审核结果)
    private void simulateUserAction(RiskContext context, RiskLevel level) {
        if (level == RiskLevel.MEDIUM) {
            // 中风险:模拟用户完成验证
            System.out.println("\n=== 模拟用户输入正确验证码 ===");
            context.handleVerification("123456");
        } else if (level == RiskLevel.HIGH) {
            // 高风险:模拟审核通过
            System.out.println("\n=== 模拟人工审核通过 ===");
            context.handleReviewResult(true, "交易真实有效");
        }
    }

    // 风险等级枚举
    enum RiskLevel { LOW, MEDIUM, HIGH, CRITICAL }

    public static void main(String[] args) {
        RiskControlService service = new RiskControlService();
        // 测试中风险交易(5000-10000元)
        service.evaluateAndProcess(new PaymentRequest("PAY001", new BigDecimal("8000")), "USER123");
    }
}

3. 模式价值体现

  • 消除条件判断:风控逻辑中不再需要if (state == NORMAL) { ... } else if (state == VERIFICATION) { ... }这样的条件分支,每种状态的行为由对应类实现,代码更清晰
  • 状态流转清晰:状态转换逻辑在状态类中明确定义(如验证通过→正常状态),便于跟踪和调试风控流程
  • 易于扩展:新增风控状态(如“限额验证”)只需添加新的状态类,无需修改现有状态和环境类,符合开闭原则
  • 职责单一:每种状态的行为和转换逻辑封装在独立类中,便于单元测试(如单独测试审核状态的审核结果处理)
  • 状态行为一致:确保同一状态下的所有行为符合该状态的业务规则(如拒绝状态下任何操作都无效)

五、开源框架中状态模式的运用

Spring StateMachine为例,说明状态模式在开源框架中的典型应用:

1. 核心实现分析

Spring StateMachine是Spring框架提供的状态机组件,基于状态模式实现,用于管理复杂的状态流转场景(如订单状态、工作流)。它将状态、事件、转换规则封装为独立组件,支持状态嵌套、并行状态等高级特性。

1.1 状态与事件定义
// 定义状态枚举
public enum OrderStates {
    CREATED, PAYED, SHIPPED, DELIVERED, CANCELLED
}

// 定义事件枚举
public enum OrderEvents {
    PAY, SHIP, DELIVER, CANCEL
}
1.2 状态机配置

通过配置类定义状态转换规则:

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        // 定义所有状态
        states
            .withStates()
                .initial(OrderStates.CREATED) // 初始状态
                .states(EnumSet.allOf(OrderStates.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        // 定义状态转换规则
        transitions
            // 创建→支付:触发PAY事件
            .withExternal()
                .source(OrderStates.CREATED).target(OrderStates.PAYED)
                .event(OrderEvents.PAY)
            // 支付→发货:触发SHIP事件
            .and()
            .withExternal()
                .source(OrderStates.PAYED).target(OrderStates.SHIPPED)
                .event(OrderEvents.SHIP)
            // 其他转换规则...
            .and()
            .withExternal()
                .source(OrderStates.CREATED).target(OrderStates.CANCELLED)
                .event(OrderEvents.CANCEL);
    }
}
1.3 状态监听器

通过监听器处理状态转换时的行为:

@Component
public class OrderStateListener {

    @OnTransition(target = "PAYED")
    public void onPayed(StateContext<OrderStates, OrderEvents> context) {
        String orderId = context.getMessage().getHeaders().get("orderId", String.class);
        System.out.println("订单" + orderId + "已支付,触发库存扣减");
        // 执行支付后的业务逻辑(如扣减库存)
    }

    @OnTransition(target = "CANCELLED")
    public void onCancelled(StateContext<OrderStates, OrderEvents> context) {
        String orderId = context.getMessage().getHeaders().get("orderId", String.class);
        System.out.println("订单" + orderId + "已取消,触发库存恢复");
        // 执行取消后的业务逻辑(如恢复库存)
    }
}
1.4 状态机使用
@Service
public class OrderService {
    @Autowired
    private StateMachine<OrderStates, OrderEvents> stateMachine;

    public void payOrder(String orderId) {
        // 发送PAY事件,触发状态从CREATED→PAYED
        Message<OrderEvents> message = MessageBuilder
            .withPayload(OrderEvents.PAY)
            .setHeader("orderId", orderId)
            .build();
        stateMachine.sendEvent(message);
    }
}

2. 状态模式在Spring StateMachine中的价值

  • 状态流转可视化:支持通过图形化工具(如StateMachine Diagram)展示状态转换规则,便于理解和调试
  • 复杂状态支持:支持嵌套状态(如支付状态下包含“支付中”“支付成功”“支付失败”子状态)和并行状态(如订单同时处于“退款中”和“投诉中”)
  • 持久化支持:可将状态机状态持久化到数据库,支持状态机重启后恢复之前的状态
  • 事件驱动:通过事件触发状态转换,符合领域驱动设计(DDD)中的领域事件思想

六、总结

1. 状态模式的适用场景

  • 当对象的行为取决于其状态,且状态会频繁变化时(如订单状态、风控状态)
  • 当代码中存在大量与状态相关的条件判断(if-elseswitch-case),导致代码臃肿难以维护时
  • 当需要清晰跟踪状态流转路径,或状态转换规则复杂时(如工作流、状态机)
  • 当希望新增状态时无需修改现有代码,只需添加新的状态类时

2. 状态模式与其他模式的区别

  • 与策略模式:两者都通过委派实现行为变化,但策略模式的策略之间无依赖,状态模式的状态之间存在转换关系,前者是“算法替换”,后者是“状态驱动行为”
  • 与享元模式:状态模式中的状态类通常采用单例(享元),但享元模式专注于对象复用,状态模式专注于状态行为和转换,前者是“结构优化”,后者是“行为设计”
  • 与观察者模式:观察者模式用于对象间的通知,状态模式可结合观察者模式(状态变化时通知观察者),但状态模式的核心是状态管理,而非通知机制

3. 支付系统中的实践价值

  • 简化复杂状态逻辑:将支付、退款、风控等场景的状态逻辑拆分为独立状态类,降低代码复杂度
  • 提高可维护性:状态行为和转换规则集中在对应类中,修改某状态的行为不会影响其他状态
  • 增强可扩展性:新增支付状态(如“部分退款”“冻结”)只需添加新状态类,符合开闭原则
  • 便于状态监控:通过状态类的方法拦截,可轻松实现状态变更日志、metrics统计等监控功能
  • 确保状态一致性:状态转换逻辑在状态类中明确定义,避免因分散的条件判断导致状态不一致

4. 实践建议

  • 状态类采用单例:状态对象无状态(不存储上下文信息),使用单例可减少对象创建开销
  • 状态转换显式化:在状态类的方法中明确调用context.setState(),避免状态转换逻辑隐藏在环境类中
  • 避免状态爆炸:当状态过多时,可考虑状态分组或使用状态模式与其他模式结合(如状态模式+策略模式)
  • 结合状态机框架:复杂状态场景(如工作流)可直接使用Spring StateMachine等框架,减少重复开发

状态模式通过“状态封装与行为委派”的思想,为支付系统中复杂状态管理提供了优雅的解决方案,既消除了冗余的条件判断,又使状态流转逻辑清晰可扩展,是处理状态驱动型业务的必备设计模式。