Go有限状态机实现和实战

Go有限状态机实现和实战

在这里插入图片描述

有限状态机

什么是状态机

有限状态机(Finite State Machine, FSM)是一种用于建模系统行为的计算模型,它包含有限数量的状态,并通过事件或条件实现状态之间的转换。FSM的状态数量是有限的,因此称为有限状态机

关于有限的解释:也就是被描述的事物的状态的数量是有限的,例如开关的状态只有“开”和“关”两个;灯的状态只有“亮”和“灭”等等。

特点

状态机的状态数量是有限的,在确定的状态下,给定特定的事件,系统会转换到明确的下一个状态。

作用

使用状态机来表达状态的流转,会使语义会更加清晰,会增强代码的稳定性可控性可维护性

适用场景

面对复杂的状态流转都可以使用状态机来实现

状态机是描述系统行为的强大工具,其核心是状态、事件和状态转换。它的清晰逻辑和确定性使其在复杂流程控制、嵌入式设备、游戏开发等领域非常适用,为系统开发提供了简洁、高效的解决方案。

状态机核心概念

状态机有四个核心概念,这是所有状态机的基础

  • State:状态。一个状态机至少要包含两个状态。
  • Transition:过渡。也就是从一个状态变化为另一个状态(即状态转移)。
  • Event:事件。事件会触发状态转移,也就是状态转移的条件
  • Action:动作。事件发生以后要执行动作,即事件处理

有限状态机的工作原理如图所示,发生事件(event)后,根据当前状态(cur_state) ,决定执行的动作(action),并设置下一个状态(next_state)。

Go状态机设计

我们根据上面的概念设计一个状态机

示例:自动贩卖机状态机

在这里插入图片描述

package mainimport ("fmt"
)// 定义状态类型
type State string
//  1.State:状态。一个状态机至少要包含两个状态。
const (StateIdle     State = "Idle(空闲)"     // 空闲状态StateCoin     State = "Coin(投币)"     // 投币状态StateItem     State = "Item(选择商品)"     // 选择商品状态
)// 定义事件类型
type Event string
// 2.Event:事件。事件会触发状态转移,也就是状态转移的条件。
const (EventInsertCoin  Event = "InsertCoin"  // 投币事件EventSelectItem  Event = "SelectItem"  // 选择商品事件EventDispense    Event = "Dispense"    // 出货事件
)// 定义状态转移 
// 3.Transition:过渡。也就是从一个状态变化为另一个状态(即状态转移)。
const(Transitions = map[State]map[Event]State{StateIdle: {EventInsertCoin: StateCoin,},StateCoin: {EventSelectItem: StateItem,},StateItem: {EventDispense: StateIdle,},}
)// 定义状态机结构体
type FSM struct {currentState Statetransitions  map[State]map[Event]State // 状态-事件转换映射
}
// 创建状态机
func NewFSM() *FSM {return &FSM{currentState: StateIdle, // 初始状态transitions: Transitions,}
}// 处理事件并进行状态转换
// 4.Action:动作。事件发生以后要执行动作,即事件处理。
func (fsm *FSM) HandleEvent(event Event) {nextState, ok := fsm.transitions[fsm.currentState][event]if !ok {fmt.Printf("事件 %s 无法从状态 %s 转换\n", event, fsm.currentState)return}fmt.Printf("从状态 %s 转到状态 %s 通过事件 %s\n", fsm.currentState, nextState, event)fsm.currentState = nextState
}// 获取当前状态
func (fsm *FSM) CurrentState() State {return fsm.currentState
}func main() {fsm := NewFSM()// 测试状态机fsm.HandleEvent(EventInsertCoin)  // 投币,进入选择商品状态fsm.HandleEvent(EventSelectItem)  // 选择商品,准备出货fsm.HandleEvent(EventDispense)    // 出货,返回初始状态
}

运行结果

从状态 Idle(空闲) 转到状态 Coin(投币) 通过事件 InsertCoin(投币事件)
从状态 Coin(投币) 转到状态 Item(选择商品) 通过事件 SelectItem(选择商品事件)
从状态 Item(选择商品) 转到状态 Idle(空闲) 通过事件 Dispense(出货事件)

代码解释:

  • State(对应State) :定义了有限状态机的状态和事件。状态有 Idle、Coin、Item 和 Dispense。

  • Event(对应Event):事件有 InsertCoin、SelectItem、Dispense 和 ReturnCoins。

  • Transitions(对应Transition):定义了状态转移规则,即当某个状态收到某个事件时,会转移到另一个状态。

  • HandleEvent(对应Action):该方法处理事件,并根据当前状态和事件查找下一个状态。如果找不到对应的转换,则表示事件在当前状态下无效。

  • FSM 结构体:负责存在状态机的信息,FSM 包含当前状态和状态转换规则。transitions 映射定义了每个状态在不同事件下的下一个状态。

事件处理

我们可以将Action进行拓展,对状态机进行扩展,比如添加日志记录、超时处理、错误处理、通知等。
在这里插入图片描述
具体实现我们可以参考下面生产使用的代码。

生产使用

上面的代码是一个简单的状态机,实际使用中,需要考虑更多的因素,比如状态的初始化、状态的持久化、状态的恢复、状态的监听等。

  1. 状态的初始化:在创建状态机时,需要初始化当前状态,并设置初始状态的转换规则。

  2. 状态的持久化:在状态机中,需要将状态的转换记录持久化到数据库或者文件中,以便在系统重启时恢复状态。

  3. 状态的恢复:在系统重启时,需要根据持久化的状态记录恢复状态机。

  4. 状态的监听:在状态机中,需要监听状态的变化,以便在状态发生变化时进行通知。

  5. 状态的校验:在状态机中,需要校验状态的转换是否合法,比如不允许从空闲状态直接进入选择商品状态。

  6. 状态的错误处理:在状态机中,需要处理状态转换过程中出现的错误,比如转换失败、超时等。

  7. 状态的日志记录:在状态机中,需要记录状态的转换记录,以便后续分析

生产上我们可以使用 github.com/looplab/fsm
这个库提供了一些常用的功能,如状态的初始化、状态的持久化、状态的恢复、状态的监听等。

package fsm_demoimport ("context""fmt""testing""github.com/looplab/fsm"
)// 定义状态类型
type State string// 1.State:状态。一个状态机至少要包含两个状态。
const (StateIdle State = "Idle(空闲)"   // 空闲状态StateCoin State = "Coin(投币)"   // 投币状态StateItem State = "Item(选择商品)" // 选择商品状态
)// 定义事件类型
type Event string// 2. Event:事件。事件会触发状态转移,也就是状态转移的条件。
const (EventInsertCoin Event = "InsertCoin" // 投币事件EventSelectItem Event = "SelectItem" // 选择商品事件EventDispense   Event = "Dispense"   // 出货事件
)type FSM struct {Id  string   // 数据idFSM *fsm.FSM // 状态机
}func NewFSM(id string) *FSM {d := &FSM{Id: id,}d.FSM = fsm.NewFSM(string(StateIdle),// 3. Transition 定义转换fsm.Events{{Name: string(EventInsertCoin), Src: []string{string(StateIdle)}, Dst: string(StateCoin)},{Name: string(EventSelectItem), Src: []string{string(StateCoin)}, Dst: string(StateItem)},{Name: string(EventDispense), Src: []string{string(StateItem)}, Dst: string(StateIdle)},},// 4. Action 定义动作fsm.Callbacks{"before_event": func(ctx context.Context, e *fsm.Event) {fmt.Println(e.Args[0])e.FSM.SetMetadata("error", "error")fmt.Println("before_event 通用(可以记录日志)", e.Event, e.FSM.Current())e.Err = fmt.Errorf("error")e.Cancel()// e.Event = "error"},fmt.Sprintf("before_%s", EventInsertCoin): func(ctx context.Context, e *fsm.Event) {fmt.Println("before", EventInsertCoin, e.Event, e.FSM.Current())// e.Err = fmt.Errorf("error")},fmt.Sprintf("enter_%s", StateCoin): func(ctx context.Context, e *fsm.Event) {fmt.Println("enter", StateCoin, e.Event, e.FSM.Current())// e.Err = fmt.Errorf("error")},"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },fmt.Sprintf("after_%s", EventInsertCoin): func(ctx context.Context, e *fsm.Event) {fmt.Println("after", EventInsertCoin, e.Event, e.FSM.Current())// e.Err = fmt.Errorf("error")},"after_event": func(_ context.Context, e *fsm.Event) {fmt.Println(e.FSM.Metadata("error"))fmt.Println("after_event 通用(可以记录日志)", e.Event, e.FSM.Current())},},)return d
}// 更新状态
func (d *FSM) enterState(e *fsm.Event) {// called after entering all states// todo 更新数据库,持久化fmt.Println("enter_state 通用 更新数据库", e.FSM.Current())
}func Test_Main(t *testing.T) {plan := NewFSM(string(StateIdle))err := plan.FSM.Event(context.Background(), string(EventInsertCoin), 1)if err != nil {fmt.Println(err)}fmt.Println(plan.FSM.Current())err = plan.FSM.Event(context.Background(), string(EventSelectItem))if err != nil {fmt.Println(err)}fmt.Println(plan.FSM.Current())err = plan.FSM.Event(context.Background(), string(EventDispense))if err != nil {fmt.Println(err)}fmt.Println(plan.FSM.Current())err = plan.FSM.Event(context.Background(), string(EventDispense))if err != nil {fmt.Println(err)}fmt.Println(plan.FSM.Current())
}

使用去起来和我们自己写的代码类似,也是定义 State ,Evnent,Transition,Action。这个是所有FSM通用的。

这个库可以很灵活的配置事件处理。可以配置很多前置和后置事件。

// 1. before_<EVENT> - 在名为 <EVENT> 的事件之前调用
// 2. before_event - 在所有事件之前调用
// 3. leave_<OLD_STATE> - 在离开 <OLD_STATE> 之前调用
// 4. leave_state - 在离开所有状态之前调用
// 5. enter_<NEW_STATE> - 在进入 <NEW_STATE> 之后调用
// 6. enter_state - 在进入所有状态之后调用
// 7. after_<EVENT> - 在名为 <EVENT> 的事件之后调用
// 8. after_event - 在所有事件之后调用

而且错误处理机制也很完善。使用这个库后大家可以把更多的精力关注业务层。

参考:

https://github.com/looplab/fsm

https://www.cnblogs.com/huageyiyangdewo/p/17351310.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/491811.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iPhone恢复技巧:如何从 iPhone 恢复丢失的照片

在计算机时代&#xff0c;我们依靠手机来捕捉和存储珍贵的回忆。但是&#xff0c;如果您不小心删除或丢失了手机上的照片怎么办&#xff1f;这真的很令人沮丧和烦恼&#xff0c;不是吗&#xff1f;好吧&#xff0c;如果您在 iPhone 上丢失了照片&#xff0c;您不必担心&#xf…

Linux高性能服务器编程 | 读书笔记 | 6. 高性能服务器程序框架

6. 高性能服务器程序框架 《Linux 高性能服务器编程》一书中&#xff0c;把这一章节作为全书的核心&#xff0c;同时作为后续章节的总览。这也意味着我们在经历了前置知识的学习后&#xff0c;正式进入了 Web 服务器项目的核心部分的学习 文章目录 6. 高性能服务器程序框架1.服…

前端的知识(部分)

11 前端的编写步骤 第一步:在HTML的页面中声明方法 第二步:在<script>中定义一个函数,其中声明一个data来为需要的数据 赋值一个初始值 第三步:编写这个方法实现对应的功能

Xcode

info.plist Appearance Light 关闭黑暗模式 Bundle display name 设置app名称&#xff0c;默认为工程名 Location When In Use Usage Description 定位权限一共有3个key 1.Privacy - Location When In Use Usage Description 2.Privacy - Location Always and When In U…

C# 中的Task

文章目录 前言一、Task 的基本概念二、创建 Task使用异步方法使用 Task.Run 方法 三、等待 Task 完成使用 await 关键字使用 Task.Wait 方法 四、处理 Task 的异常使用 try-catch 块使用 Task.Exception 属性 五、Task 的延续使用 ContinueWith 方法使用 await 关键字和异步方法…

【Java 学习】:内部类详解

详谈Java内部类 &#x1f4c3;&#x1f4c3;本文将通过Java内部类 是什么&#xff0c;为什么被广泛使用&#xff0c;以及又该如何去使用这三个方面来详细讲解其相关知识。 文章目录 1. 内部类是什么 2. 为什么要使用内部类 3. 如何使用内部类 &#x1f349;成员内部类 &…

如何解决samba服务器共享文件夹不能粘贴文件

sudo vim /etc/samba/smb.conf在samba的配置文件中增加一个选项 writable yes重启Samba服务以使更改生效&#xff1a; sudo service smbd restart

PyTorch3D 可视化

PyTorch3D是非常好用的3D工具库。但是PyTorch3D对于可用于debug&#xff08;例如调整cameras参数&#xff09;的可视化工具并没有进行系统的介绍。这篇文章主要是想介绍我觉得非常使用的PyTorch3D可视化工具。 1. 新建一个Mesh 从hugging face上下载一个glb文件&#xff0c;例…

RabbitMQ的核心组件有哪些?

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ的核心组件有哪些&#xff1f;】面试题。希望对大家有帮助&#xff1b; RabbitMQ的核心组件有哪些&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RabbitMQ是一个开源的消息代理&#xff08;Messag…

SM4笔记整理

文章目录 1. 介绍2. 算法定义3. 迭代运算3.1 轮函数F3.2 合成置换T 4. SM4秘钥生成4.1 具体步骤4.2 系统参数FK4.2 固定参数CK 5. 参考资料 以下内容为信息安全开发过程中&#xff0c;SM4对称加密算法的笔记。 对称加密算法汇总介绍&#xff1a;对称加密算法和模式 1. 介绍 …

如何使用Git or SVN--可视化工具

不会吧 现在还有人不会Git可视化工具 &#xff0c;作为一个从命令行转为可视化的开发者&#xff0c;深知这个可视化工具的重要性&#xff0c;之前在命令行去维护我们的工程是一个很头疼的事情 &#xff0c;后面也就有了可视化工具&#xff0c;市面上的工具的教程都不是很详细哥…

基于Clinical BERT的医疗知识图谱自动化构建方法,双层对比框架

基于Clinical BERT的医疗知识图谱自动化构建方法&#xff0c;双层对比框架 论文大纲理解1. 确认目标2. 目标-手段分析3. 实现步骤4. 金手指分析 全流程核心模式核心模式提取压缩后的系统描述核心创新点 数据分析第一步&#xff1a;数据收集第二步&#xff1a;规律挖掘第三步&am…

《Time Ghost》的制作:使用 DOTS ECS 制作更为复杂的大型环境

*基于 Unity 6 引擎制作的 demo 《Time Ghost》 开始《Time Ghost》项目时的目标之一是提升在 Unity 中构建大型户外环境的构建标准。为了实现这一目标&#xff0c;我们要有处理更为复杂的场景的能力、有足够的工具支持&#xff0c;同时它对引擎的核心图形、光照、后处理、渲染…

华为大数据_unittest单元测试框架解析与应用

一、引言 随着软件开发的复杂度日益增加&#xff0c;单元测试在软件质量保证体系中扮演着越来越重要的角色。unittest作为Python的标准单元测试框架&#xff0c;以其简单、易用和强大的特性&#xff0c;受到了广大开发者的青睐。本文旨在深入解析unittest框架的核心原理&#…

修改uniapp下拉刷新圆圈颜色

直接看图 修改前就是常规的绿色 自定义更符合我们的软件 直接说方法 修改 在App.vue的style样式里添加一行 .uni-page-refresh--refreshing .uni-page-refresh__path{stroke:#FF2442; }我是通过 不执行 uni.stopPullDownRefresh(); 下拉刷新 之后通过F12看出来的 希望可以帮…

大屏开源项目go-view二次开发3----象形柱图控件(C#)

环境搭建参考&#xff1a; 大屏开源项目go-view二次开发1----环境搭建(C#)-CSDN博客 要做的象形柱图控件最终效果如下图&#xff1a; 其实这个控件我前面的文章也介绍过&#xff0c;不过是用wpf做的&#xff0c;链接如下&#xff1a; wpf利用Microsoft.Web.WebView2显示html…

MAC虚拟机上安装WDA环境

MAC虚拟机上安装WDA环境 一、MAC虚拟机切换root权限二、macOS上安装xcode若你的macOS系统可以在appstore下载安装若你安装的macOS系统版本太低&#xff0c;无法在appstore上安装xcode 三、macOS上安装WebDriverAgent四、使用xcode配置WDA安装到手机上高版本系统支持 一、MAC虚拟…

解决 Git Permission denied 问题

前言 push项目时出现gitgithub.com: Permission denied (publickey). fatal: Could not read from remote repository.Please make sure you have the correct access rights and the repository exists.出现这个问题表示你在尝试将本地代码推送到GitHub时&#xff0c;没有提供…

【工具】linux matlab 的使用

问题1 - 复制图表 在使用linux matlab画图后&#xff0c;无法保存figure。 例如在windows下 但是在linux下并没有这个“Copy Figure”的选项。 这是因为 “ The Copy Figure option is not available on Linux systems. Use the programmatic alternative.” 解决方案&…

Oracle最佳实践-优化硬解析

前段时间参加oracle CAB&#xff0c;oracle高级服务部门做了一个数据库最佳实践的报告&#xff0c;其中就有一项就是解决未使用绑定变量但执行次数很多的SQL&#xff1b; 对于一个数据库来说如果不知道该如何优化&#xff0c;那么最简单最有效的优化就是减少硬解析&#xff0c;…