3. 状态机中的守卫条件
任务说明
在订单状态机中,当用户尝试从“待支付”转换到“已支付”时,需要校验库存是否充足。若库存不足,转换必须被拒绝,状态保持为“待支付”,并返回明确的失败原因。本案例实现一个带守卫条件的订单状态机核心逻辑。
核心实现
// order_state_machine.go
package main
import (
"errors"
"fmt"
)
// 订单状态
type OrderState int
const (
PendingPayment OrderState = iota // 待支付
Paid // 已支付
Shipped // 已发货
Completed // 已完成
Cancelled // 已取消
)
func (s OrderState) String() string {
switch s {
case PendingPayment:
return "待支付"
case Paid:
return "已支付"
case Shipped:
return "已发货"
case Completed:
return "已完成"
case Cancelled:
return "已取消"
default:
return "未知"
}
}
// 订单实体
type Order struct {
ID string
State OrderState
ProductStock int // 商品库存(简化:每个订单关联一个商品库存)
}
// 状态转换守卫条件
type Guard func(order *Order) error
// 转换定义:from -> to 及其守卫
type Transition struct {
From OrderState
To OrderState
Guards []Guard
}
// 状态机
type StateMachine struct {
Transitions []Transition
}
// 注册转换
func (sm *StateMachine) AddTransition(from, to OrderState, guards ...Guard) {
sm.Transitions = append(sm.Transitions, Transition{
From: from, To: to, Guards: guards,
})
}
// 执行转换
func (sm *StateMachine) TransitionTo(order *Order, target OrderState) error {
// 查找匹配的转换规则
var matched *Transition
for i := range sm.Transitions {
t := &sm.Transitions[i]
if t.From == order.State && t.To == target {
matched = t
break
}
}
if matched == nil {
return fmt.Errorf("非法转换: %s -> %s", order.State, target)
}
// 执行所有守卫条件
for _, guard := range matched.Guards {
if err := guard(order); err != nil {
return err // 守卫失败,拒绝转换
}
}
// 守卫全部通过,执行转换
order.State = target
return nil
}
// 守卫:库存充足检查
func StockSufficientGuard(order *Order) error {
if order.ProductStock <= 0 {
return errors.New("库存不足,无法支付")
}
return nil
}
func main() {
// 构建状态机
sm := &StateMachine{}
// 待支付 -> 已支付(需要库存守卫)
sm.AddTransition(PendingPayment, Paid, StockSufficientGuard)
// 已支付 -> 已发货
sm.AddTransition(Paid, Shipped)
// 已发货 -> 已完成
sm.AddTransition(Shipped, Completed)
// 待支付 -> 已取消
sm.AddTransition(PendingPayment, Cancelled)
// 场景1:库存充足,支付成功
order1 := &Order{ID: "A001", State: PendingPayment, ProductStock: 5}
err := sm.TransitionTo(order1, Paid)
if err != nil {
fmt.Printf("订单%s支付失败: %v\n", order1.ID, err)
} else {
fmt.Printf("订单%s支付成功,当前状态: %s\n", order1.ID, order1.State)
}
// 场景2:库存不足,支付被拒绝
order2 := &Order{ID: "B002", State: PendingPayment, ProductStock: 0}
err = sm.TransitionTo(order2, Paid)
if err != nil {
fmt.Printf("订单%s支付失败: %v(状态保持: %s)\n", order2.ID, err, order2.State)
} else {
fmt.Printf("订单%s支付成功,当前状态: %s\n", order2.ID, order2.State)
}
}
运行与验证
- 保存代码为
order_state_machine.go,在终端执行:
go run order_state_machine.go
- 预期输出(与代码逻辑严格对应):
订单A001支付成功,当前状态: 已支付
订单B002支付失败: 库存不足,无法支付(状态保持: 待支付)
判定通过条件:order1 状态变为“已支付”;order2 状态仍为“待支付”,且返回错误信息包含“库存不足”。
关键设计说明
- 守卫条件以函数切片形式挂载在转换规则上,新增守卫只需追加函数,无需修改状态机核心逻辑。
- 守卫返回
error即中断转换,状态保持不变;返回nil则继续执行后续守卫,全部通过后完成状态迁移。 - 非法转换(未注册的 from→to 组合)会被直接拒绝,保证状态流转受控。
图中红色虚线表示当“库存不足”守卫失败时,转换被拒绝,订单状态回退并保持在“待支付”,不会进入“已支付”状态。