状态模式:设计与实践
一、什么是状态模式
1. 基本定义
状态模式(State Pattern)是一种行为型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
该模式通过将对象的每个状态封装为独立的状态类,使对象在不同状态下的行为由对应状态类负责,而非通过大量条件判断实现。核心是将“状态判断”与“行为实现”分离,当对象状态改变时,其行为会随之改变,就像对象的类发生了变化一样。
2. 核心思想
状态模式的核心在于状态封装与行为委派。当对象的行为取决于其状态,且状态会频繁变化时,通过将每个状态的行为封装为独立类,可消除代码中的条件分支(如if-else或switch-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-else或switch-case),导致代码臃肿难以维护时 - 当需要清晰跟踪状态流转路径,或状态转换规则复杂时(如工作流、状态机)
- 当希望新增状态时无需修改现有代码,只需添加新的状态类时
2. 状态模式与其他模式的区别
- 与策略模式:两者都通过委派实现行为变化,但策略模式的策略之间无依赖,状态模式的状态之间存在转换关系,前者是“算法替换”,后者是“状态驱动行为”
- 与享元模式:状态模式中的状态类通常采用单例(享元),但享元模式专注于对象复用,状态模式专注于状态行为和转换,前者是“结构优化”,后者是“行为设计”
- 与观察者模式:观察者模式用于对象间的通知,状态模式可结合观察者模式(状态变化时通知观察者),但状态模式的核心是状态管理,而非通知机制
3. 支付系统中的实践价值
- 简化复杂状态逻辑:将支付、退款、风控等场景的状态逻辑拆分为独立状态类,降低代码复杂度
- 提高可维护性:状态行为和转换规则集中在对应类中,修改某状态的行为不会影响其他状态
- 增强可扩展性:新增支付状态(如“部分退款”“冻结”)只需添加新状态类,符合开闭原则
- 便于状态监控:通过状态类的方法拦截,可轻松实现状态变更日志、metrics统计等监控功能
- 确保状态一致性:状态转换逻辑在状态类中明确定义,避免因分散的条件判断导致状态不一致
4. 实践建议
- 状态类采用单例:状态对象无状态(不存储上下文信息),使用单例可减少对象创建开销
- 状态转换显式化:在状态类的方法中明确调用
context.setState(),避免状态转换逻辑隐藏在环境类中 - 避免状态爆炸:当状态过多时,可考虑状态分组或使用状态模式与其他模式结合(如状态模式+策略模式)
- 结合状态机框架:复杂状态场景(如工作流)可直接使用Spring StateMachine等框架,减少重复开发
状态模式通过“状态封装与行为委派”的思想,为支付系统中复杂状态管理提供了优雅的解决方案,既消除了冗余的条件判断,又使状态流转逻辑清晰可扩展,是处理状态驱动型业务的必备设计模式。