在C#的业务开发中,订单状态流转是非常常见的场景,从待支付、已支付、待发货、已发货到已完成,每个状态之间的转移都有明确的规则。如果用传统的条件判断实现,代码会变得越来越臃肿,后期维护成本很高。而使用Stateless库实现状态机,可以把状态和转移规则清晰地定义出来,让逻辑更可控。

Stateless库核心概念
Stateless库的核心是StateMachine<TState,TTrigger>类,其中TState表示状态类型,TTrigger表示触发状态转移的触发条件类型。它的几个核心组成部分如下:
- 状态(State):对象当前所处的阶段,比如订单的待支付状态
- 触发器(Trigger):触发状态转移的事件,比如支付成功、发货操作
- 状态转移配置:定义某个状态下,触发某个触发器后跳转到哪个新状态
- 状态监听:可以在状态进入、离开时执行自定义逻辑
订单状态流转场景定义
我们先定义订单的所有状态和对应的触发器,这里用枚举来定义更清晰:
// 订单状态枚举
public enum OrderState
{
PendingPayment, // 待支付
Paid, // 已支付
Shipped, // 已发货
Completed, // 已完成
Cancelled // 已取消
}
// 触发状态转移的触发器枚举
public enum OrderTrigger
{
Pay, // 支付
Ship, // 发货
ConfirmReceive, // 确认收货
Cancel, // 取消订单
Refund // 退款
}
用Stateless实现订单状态机
1. 初始化状态机
首先创建状态机实例,指定初始状态为待支付:
using Stateless;
public class OrderStateMachine
{
private readonly StateMachine<OrderState, OrderTrigger> _stateMachine;
public OrderState CurrentState => _stateMachine.State;
public OrderStateMachine()
{
// 初始化状态机,初始状态为待支付
_stateMachine = new StateMachine<OrderState, OrderTrigger>(OrderState.PendingPayment);
}
}
2. 配置状态转移规则
接下来配置每个状态下的合法转移规则,比如待支付状态下只能触发支付或者取消订单:
public class OrderStateMachine
{
private readonly StateMachine<OrderState, OrderTrigger> _stateMachine;
public OrderState CurrentState => _stateMachine.State;
public OrderStateMachine()
{
_stateMachine = new StateMachine<OrderState, OrderTrigger>(OrderState.PendingPayment);
ConfigureStateTransitions();
}
private void ConfigureStateTransitions()
{
// 待支付状态的转移规则
_stateMachine.Configure(OrderState.PendingPayment)
.Permit(OrderTrigger.Pay, OrderState.Paid) // 支付成功跳转到已支付
.Permit(OrderTrigger.Cancel, OrderState.Cancelled); // 取消订单跳转到已取消
// 已支付状态的转移规则
_stateMachine.Configure(OrderState.Paid)
.Permit(OrderTrigger.Ship, OrderState.Shipped) // 发货跳转到已发货
.Permit(OrderTrigger.Refund, OrderState.Cancelled); // 退款跳转到已取消
// 已发货状态的转移规则
_stateMachine.Configure(OrderState.Shipped)
.Permit(OrderTrigger.ConfirmReceive, OrderState.Completed); // 确认收货跳转到已完成
// 已完成和已取消状态为终态,没有后续转移规则
}
}
3. 添加状态监听逻辑
如果需要在状态变化的时候执行额外逻辑,比如状态变更时记录日志,可以添加状态进入和离开的监听:
private void ConfigureStateTransitions()
{
// 待支付状态的转移规则
_stateMachine.Configure(OrderState.PendingPayment)
.Permit(OrderTrigger.Pay, OrderState.Paid)
.Permit(OrderTrigger.Cancel, OrderState.Cancelled)
.OnLeave(state => Console.WriteLine($"离开待支付状态,目标状态:{state}"));
// 已支付状态的转移规则
_stateMachine.Configure(OrderState.Paid)
.Permit(OrderTrigger.Ship, OrderState.Shipped)
.Permit(OrderTrigger.Refund, OrderState.Cancelled)
.OnEntry(state => Console.WriteLine($"进入已支付状态,原状态:{state}"));
}
4. 触发状态转移
状态机配置完成后,就可以在业务代码中触发对应的触发器来改变订单状态了:
public class OrderService
{
private readonly OrderStateMachine _stateMachine;
public OrderService()
{
_stateMachine = new OrderStateMachine();
}
// 支付订单
public void PayOrder()
{
if (_stateMachine.CurrentState == OrderState.PendingPayment)
{
// 触发支付触发器
_stateMachine.Fire(OrderTrigger.Pay);
Console.WriteLine($"支付成功,当前订单状态:{_stateMachine.CurrentState}");
}
else
{
Console.WriteLine("当前订单状态不允许支付");
}
}
// 发货
public void ShipOrder()
{
if (_stateMachine.CurrentState == OrderState.Paid)
{
_stateMachine.Fire(OrderTrigger.Ship);
Console.WriteLine($"发货成功,当前订单状态:{_stateMachine.CurrentState}");
}
else
{
Console.WriteLine("当前订单状态不允许发货");
}
}
}
状态机合法性校验
Stateless还提供了状态合法性校验的方法,可以在初始化完成后调用,检查是否有未配置转移规则的状态:
public OrderStateMachine()
{
_stateMachine = new StateMachine<OrderState, OrderTrigger>(OrderState.PendingPayment);
ConfigureStateTransitions();
// 校验状态转移配置是否完整
var errors = _stateMachine.GetErrors();
if (errors.Any())
{
foreach (var error in errors)
{
Console.WriteLine($"状态机配置错误:{error}");
}
}
}
注意事项
- 触发器触发前最好先判断当前状态是否允许该操作,避免触发未定义的转移抛出异常
- 如果状态或者触发器很多,可以把配置拆分到不同的方法中,提高代码可读性
- 状态机的状态可以持久化存储,比如把当前状态存到数据库,下次加载的时候初始化状态机为存储的状态即可