golang创建状态机的流畅库插件stateless的使用

Golang创建状态机的流畅库插件stateless的使用

Stateless logo. Fire gopher designed by https://www.deviantart.com/quasilyte

介绍

Stateless是一个用于在Go代码中直接创建状态机和轻量级基于状态机的工作流的库。它基于UML状态图理论,帮助组织设备、计算机程序或其他技术流程的工作方式,使实体或其子实体始终处于多个可能状态中的一个,并且在这些状态之间有明确定义的条件转换。

主要特性

  • 支持任何可比较类型的状态和触发器(int, strings, boolean, structs等)
  • 分层状态
  • 状态的进入/退出事件
  • 支持条件转换的守卫条件
  • 自省功能
  • 外部状态存储
  • 参数化触发器
  • 可重入状态
  • 线程安全
  • 导出为DOT图

完整示例

下面是一个电话呼叫状态机的完整示例:

package main

import (
	"context"
	"fmt"
	"github.com/qmuntal/stateless"
)

// 定义状态
const (
	stateOffHook = iota
	stateRinging
	stateConnected
	stateOnHold
	statePhoneDestroyed
)

// 定义触发器
const (
	triggerCallDialed = iota
	triggerCallConnected
	triggerLeftMessage
	triggerPlacedOnHold
	triggerTakenOffHold
	triggerPhoneHurledAgainstWall
)

func main() {
	// 创建状态机,初始状态为挂机状态
	phoneCall := stateless.NewStateMachine(stateOffHook)

	// 配置状态转换
	phoneCall.Configure(stateOffHook).
		Permit(triggerCallDialed, stateRinging)

	phoneCall.Configure(stateRinging).
		OnEntryFrom(triggerCallDialed, func(_ context.Context, args ...any) error {
			fmt.Println("拨号中... 呼叫号码:", args[0].(string))
			return nil
		}).
		Permit(triggerCallConnected, stateConnected)

	phoneCall.Configure(stateConnected).
		OnEntry(func(_ context.Context, _ ...any) error {
			fmt.Println("开始通话计时...")
			return nil
		}).
		OnExit(func(_ context.Context, _ ...any) error {
			fmt.Println("停止通话计时...")
			return nil
		}).
		Permit(triggerLeftMessage, stateOffHook).
		Permit(triggerPlacedOnHold, stateOnHold)

	phoneCall.Configure(stateOnHold).
		SubstateOf(stateConnected).
		Permit(triggerTakenOffHold, stateConnected).
		Permit(triggerPhoneHurledAgainstWall, statePhoneDestroyed)

	// 触发状态转换
	err := phoneCall.Fire(triggerCallDialed, "1234567890")
	if err != nil {
		fmt.Println("触发拨号失败:", err)
		return
	}

	err = phoneCall.Fire(triggerCallConnected)
	if err != nil {
		fmt.Println("触发连接失败:", err)
		return
	}

	err = phoneCall.Fire(triggerPlacedOnHold)
	if err != nil {
		fmt.Println("触发保持失败:", err)
		return
	}

	fmt.Println("当前状态:", phoneCall.MustState())
}

分层状态示例

phoneCall.Configure(stateOnHold).
    SubstateOf(stateConnected).  // OnHold是Connected的子状态
    Permit(triggerTakenOffHold, stateConnected).
    Permit(triggerPhoneHurledAgainstWall, statePhoneDestroyed)

进入/退出事件

phoneCall.Configure(stateConnected).
    OnEntry(func(_ context.Context, _ ...any) error {
        startCallTimer()
        return nil
    }).
    OnExit(func(_ context.Context, _ ...any) error {
        stopCallTimer()
        return nil
    })

外部状态存储

machine := stateless.NewStateMachineWithExternalStorage(
    func(_ context.Context) (stateless.State, error) {
        return myState.Value, nil
    },
    func(_ context.Context, state stateless.State) error {
        myState.Value = state
        return nil
    },
    stateless.FiringQueued)

守卫条件

phoneCall.Configure(stateOffHook).
    Permit(triggerCallDialed, stateRinging, func(_ context.Context, _ ...any) bool {
        return IsValidNumber()
    }).
    Permit(triggerCallDialed, stateBeeping, func(_ context.Context, _ ...any) bool {
        return !IsValidNumber()
    })

参数化触发器

stateMachine.SetTriggerParameters(triggerCallDialed, reflect.TypeOf(""))

stateMachine.Configure(stateRinging).
    OnEntryFrom(triggerCallDialed, func(_ context.Context, args ...any) error {
        fmt.Println(args[0].(string))
        return nil
    })

stateMachine.Fire(triggerCallDialed, "qmuntal")

导出为DOT图

sm := stateMachine.Configure(stateOffHook).
    Permit(triggerCallDialed, stateRinging, isValidNumber)
graph := sm.ToGraph()

生成的DOT图示例:

digraph {
  OffHook -> Ringing [label="CallDialled [isValidNumber]"];
}

Phone Call graph

项目目标

Stateless旨在保持简洁和最小化,同时提供强大的状态机功能。它实现了关于状态转换的规则集,但在使用委托版本的构造函数时,它本身不维护任何内部状态。


更多关于golang创建状态机的流畅库插件stateless的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang创建状态机的流畅库插件stateless的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用 stateless 库在 Go 中创建状态机

stateless 是一个轻量级的状态机库,可以帮助你在 Go 中实现复杂的状态转换逻辑。下面我将详细介绍如何使用这个库。

安装 stateless

首先安装 stateless 库:

go get github.com/qmuntal/stateless

基本概念

stateless 库基于以下几个核心概念:

  • 状态(State): 系统所处的当前状态
  • 触发器(Trigger): 触发状态转换的事件
  • 转换(Transition): 从一个状态到另一个状态的转变
  • 守卫(Guard): 决定是否允许转换的条件

基本使用示例

下面是一个简单的灯泡状态机示例:

package main

import (
	"fmt"
	"github.com/qmuntal/stateless"
)

// 定义状态
const (
	StateOff = "Off"
	StateOn  = "On"
)

// 定义触发器
const (
	TriggerSwitch = "Switch"
)

func main() {
	// 创建状态机,初始状态为关闭
	sm := stateless.NewStateMachine(StateOff)
	
	// 配置状态转换
	sm.Configure(StateOff).
		Permit(TriggerSwitch, StateOn)
	
	sm.Configure(StateOn).
		Permit(TriggerSwitch, StateOff)
	
	// 打印初始状态
	fmt.Printf("初始状态: %s\n", sm.MustState())
	
	// 触发状态转换
	err := sm.Fire(TriggerSwitch)
	if err != nil {
		fmt.Println("转换错误:", err)
		return
	}
	
	fmt.Printf("触发后的状态: %s\n", sm.MustState())
	
	// 再次触发
	err = sm.Fire(TriggerSwitch)
	if err != nil {
		fmt.Println("转换错误:", err)
		return
	}
	
	fmt.Printf("再次触发后的状态: %s\n", sm.MustState())
}

高级功能

1. 带条件的转换(守卫)

sm.Configure(StateA).
	PermitIf(TriggerX, StateB, func() bool {
		return someCondition
	})

2. 状态转换时的动作

sm.Configure(StateA).
	Permit(TriggerX, StateB).
	OnEntry(func() {
		fmt.Println("进入状态B")
	}).
	OnExit(func() {
		fmt.Println("离开状态A")
	})

3. 忽略触发器

sm.Configure(StateA).
	Ignore(TriggerY)  // 在StateA时忽略TriggerY

4. 内部转换(不改变状态)

sm.Configure(StateA).
	InternalTransition(TriggerZ, func() {
		fmt.Println("处理TriggerZ但不改变状态")
	})

完整示例:订单状态机

package main

import (
	"fmt"
	"github.com/qmuntal/stateless"
)

// 订单状态
const (
	OrderCreated    = "Created"
	OrderProcessing = "Processing"
	OrderShipped    = "Shipped"
	OrderDelivered  = "Delivered"
	OrderCancelled  = "Cancelled"
)

// 订单触发器
const (
	TriggerProcess = "Process"
	TriggerShip    = "Ship"
	TriggerDeliver = "Deliver"
	TriggerCancel  = "Cancel"
)

func main() {
	sm := stateless.NewStateMachine(OrderCreated)
	
	// 配置状态转换
	sm.Configure(OrderCreated).
		Permit(TriggerProcess, OrderProcessing).
		Permit(TriggerCancel, OrderCancelled)
	
	sm.Configure(OrderProcessing).
		Permit(TriggerShip, OrderShipped).
		Permit(TriggerCancel, OrderCancelled)
	
	sm.Configure(OrderShipped).
		Permit(TriggerDeliver, OrderDelivered)
	
	// 添加状态转换时的动作
	sm.Configure(OrderCreated).
		OnExit(func() {
			fmt.Println("订单已创建,开始处理...")
		})
	
	sm.Configure(OrderProcessing).
		OnEntry(func() {
			fmt.Println("订单处理中...")
		})
	
	// 模拟订单流程
	processOrder(sm)
}

func processOrder(sm *stateless.StateMachine) {
	fmt.Printf("当前订单状态: %s\n", sm.MustState())
	
	// 处理订单
	if err := sm.Fire(TriggerProcess); err != nil {
		fmt.Println("错误:", err)
		return
	}
	fmt.Printf("订单状态: %s\n", sm.MustState())
	
	// 发货
	if err := sm.Fire(TriggerShip); err != nil {
		fmt.Println("错误:", err)
		return
	}
	fmt.Printf("订单状态: %s\n", sm.MustState())
	
	// 交付
	if err := sm.Fire(TriggerDeliver); err != nil {
		fmt.Println("错误:", err)
		return
	}
	fmt.Printf("订单状态: %s\n", sm.MustState())
}

最佳实践

  1. 将状态和触发器定义为常量:这有助于维护和避免拼写错误
  2. 集中配置状态机:在一个地方配置所有状态转换,便于管理
  3. 合理使用守卫条件:复杂的业务逻辑可以通过守卫条件实现
  4. 记录状态转换:使用OnEntry/OnExit记录重要的状态变化
  5. 处理错误:总是检查Fire()方法的返回值

总结

stateless 库提供了一种清晰、声明式的方式来管理复杂的状态转换逻辑。通过定义状态、触发器和转换规则,你可以构建易于理解和维护的状态机。该库特别适合需要严格状态管理的业务流程,如订单处理、工作流引擎等场景。

相比自己实现状态模式,使用 stateless 库可以减少样板代码,让开发者更专注于业务逻辑而非状态管理机制。

回到顶部