Go有限状态机实现和实战

Go有限状态机实现和实战

在这里插入图片描述

有限状态机

什么是状态机

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

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

特点

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

作用

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

适用场景

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

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

状态机核心概念

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

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

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

Go状态机设计

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

示例:自动贩卖机状态机

在这里插入图片描述

package main

import (
	"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 State
	transitions  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_demo

import (
	"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   // 数据id
	FSM *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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux shell的七大功能 --- history

1.直接输入“history” 这个命令可以显示出曾经使用过的命令&#xff08;最近时间的500条&#xff09; history 2.“history”命令也可以搭配其他命令一起使用。 例&#xff1a;history | grep "vim"&#xff0c;找出所有包含“vim”的记录&#xff1b; 也可以搭配…

精品基于Python实现的微信小程序校园导航系统-微信小程序

[含文档PPT源码等] [包运行成功永久免费答疑辅导] 《django微信小程序校园导航系统》该项目采用技术Python的django框架、mysql数据库 &#xff0c;项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、核心代码介绍视频等 软件开发环境及开发工具&#xf…

Ubuntu18安装后基本配置操作

1. 关掉自动更新 不关掉自动更新&#xff0c;会将你的ubuntu系统更新到更高版本&#xff0c;一些配置就不能用了&#xff0c;所以要关掉自动更新。在“软件和更新”中将“自动检查更新”设置为从不。 2. ubuntu换国内源 参考链接换源 按照这个换源这个换源好使 &#xff0c;…

FastAPI简介

FastAPI简介 一、FastAPI简介二、FastAPI安装2.1 使用pip安装FastAPI2.2 FastAPI的demo2.3 FastAPI的程序结构 三、装饰器请求方法四、用户请求4.1 路径参数4.1.1 单个路径参数4.1.2 多个路径参数4.1.3 固定路径和路径参数的冲突 4.2 查询参数4.3 默认参数4.4 可选参数 五、请求…

关于Postgresql旧版本安装

抛出问题 局点项目现场&#xff0c;要求对如下三类资产做安全加固&#xff0c;需要在公司侧搭建测试验证环境&#xff0c;故有此篇。 bclinux 8.2 tomcat-8.5.59 postgrel -11 随着PG迭代&#xff0c;老旧版本仅提供有限维护。如果想安装老版本可能就要费劲儿一些。现在&…

金融信息分析基础(1)

1.金融数据 金融数据分为&#xff1a;交易数据&#xff08;低频数据&#xff0c;高频数据&#xff0c;超高频数据&#xff09;&#xff0c;报表数据&#xff08;财务报表&#xff0c;研报&#xff09;&#xff0c;金融社交媒体数据 低频数据&#xff1a; 以日、周、月、季、年…

C# 网络编程--关于UDP 通信(二)

UDP (User Datagram Protocol) 是一种无连接的传输层协议&#xff0c;主要用于支持数据报文的传输。它的主要特点包括简单、高效、不保证可靠性和顺序。 1.UDP协议基本概念 1.udp基于IP的简单的协议&#xff0c;不可靠的协议 2.优点&#xff1a;简单、 轻量化、 传输速度高、…

1 汇编语言

课程概要 人与人沟通需要使用到语言&#xff0c;人与计算机沟通也需要一种语言进行&#xff0c;你要跟计算机进行沟通&#xff0c;必须要使用计算机可以识别的语言&#xff0c;这种语言我们称之为机器语言&#xff0c;也就是0和1&#xff0c;二进制。 但对于人来说机器语言&a…

每天40分玩转Django:简介和环境搭建

Django简介和环境搭建 一、课程概述 学习项目具体内容预计用时Django概念Django框架介绍、MVC/MTV模式、Django特点60分钟环境搭建Python安装、pip配置、Django安装、IDE选择45分钟创建项目项目结构、基本配置、运行测试75分钟实战练习创建个人博客项目框架60分钟 二、Djang…

AI学习记录 - 依据 minimind 项目入门

想学习AI&#xff0c;还是需要从头到尾跑一边流程&#xff0c;最近看到这个项目 minimind, 我也记录下学习到的东西&#xff0c;需要结合项目的readme看。 1、github链接 https://github.com/jingyaogong/minimind?tabreadme-ov-file 2、硬件环境&#xff1a;英伟达4070ti …

对象键值对的修改

一&#xff1a;一个对象&#xff0c;过滤掉键对应的值是空数组的键&#xff0c;保留值不是空数组的键值对 const obj {a: [1, 2, 3],b: [],c: [4, 5],d: [],e: [6] };// 过滤掉值为空数组的键值对 const filteredObj Object.fromEntries(Object.entries(obj).filter(([key, v…

Java基础知识(四) -- 面向对象(中)

1.封装 1.1 概述 面向对象编程语言是对客观世界的模拟&#xff0c;客观世界里每一个事物的内部信息都是隐藏在对象内部的&#xff0c;外界无法直接操作和修改&#xff0c;只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障&#xff0c;防止该类的代码和数据被其…

02. Docker:安装和操作

目录 一、Docker的安装方式 1、实验环境准备 1.1 关闭防火墙 1.2 可以访问网络 1.3 配置yum源 2、yum安装docker 2.1 安装docker服务 2.2 配置镜像加速 2.3 启动docker服务 3、二进制安装docker 3.1 下载或上传安装包并解压 3.2 配置使用systemctl管理 3.3 配置镜像…

【人工智能】OpenAI O1模型:超越GPT-4的长上下文RAG性能详解与优化指南

在人工智能&#xff08;AI&#xff09;领域&#xff0c;长上下文生成与检索&#xff08;RAG&#xff09; 已成为提升自然语言处理&#xff08;NLP&#xff09;模型性能的关键技术之一。随着数据规模与应用场景的不断扩展&#xff0c;如何高效地处理海量上下文信息&#xff0c;成…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍01

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

华为FreeBuds Pro 4丢了如何找回?(附查找功能使用方法)

华为FreeBuds Pro 4查找到底怎么用&#xff1f;华为FreeBuds Pro 4有星闪精确查找和离线查找&#xff0c;离线查找功能涵盖播放铃声、导航定位、星闪精确查找、上线通知、丢失模式、遗落提醒等。星闪精确查找是离线查找的子功能&#xff0c;当前仅华为FreeBuds Pro 4充电盒支持…

Python爬虫之Scrapy框架基础入门

Scrapy 是一个用于Python的开源网络爬虫框架&#xff0c;它为编写网络爬虫来抓取网站数据并提取结构化信息提供了一种高效的方法。Scrapy可以用于各种目的的数据抓取&#xff0c;如数据挖掘、监控和自动化测试等。 【1】安装 pip install scrapy安装成功如下所示&#xff1a;…

【电子元器件】电感基础知识

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、 电感的基本工作原理 1. 电感的基本工作原理如下&#xff1a; &#xff08;1&#xff09; 当线圈中有电流通过时&#…

OpenGL ES详解——多个纹理实现混叠显示

目录 一、获取图片纹理数据 二、着色器编写 1. 顶点着色器 2. 片元着色器 三、绑定和绘制纹理 1. 绑定纹理 2. 绘制纹理 四、源码下载 一、获取图片纹理数据 获取图片纹理数据代码如下&#xff1a; //获取图片1纹理数据 mTextureId loadTexture(mContext, R.mipmap.…

C#,在 C# 语言中将 LaTeX 转换为 PNG 或 JPG 图像

在 C 语言中将 LaTeX 转换为 PNG 或 JPG 图像# 12月 28&#xff0c; 2021 2 分钟 法尔汉拉扎 在 C 语言中将 TeX 转换为 PNG JPG 图像# TeX 格式用于处理技术和科学文件。它通常用于交流或发布此类文档。在某些情况下&#xff0c;您可能需要将 TeX 文件渲染为 PNG 或 JPG 等图像…