Go中的有限状态机FSM的详细介绍 _

news2024/12/25 1:59:37

1、FSM简介

1.1 有限状态机的定义

有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件。

状态机有三个组成部分:状态(State)、事件(Event)、动作(Action),事件(转移条件)触发状态的转移和动作的执行。动作的执行不是必须的,可以只转移状态,不指定任何动作。总体而言,状态机是一种用以表示有限个状态以及这些状态之间的转移和动作的执行等行为的数学模型。

状态机可以用公式 State(S) , Event(E) -> Actions (A), State(S’)表示,即在处于状态S的情况下,接收到了事件E,使得状态转移到了S’,同时伴随着动作A的执行。

Event(事件)是指触发状态转换的输入信号或条件。它可以是任何类型的输入,例如传感器数据、用户输入、网络消息等。在编程中,Event通常是一个枚举类型,每个枚举值代表一个特定的事件。

State(状态)是指系统在某一时刻所处的状态,它是系统的一种抽象描述。在有限状态机中,状态是由一组状态变量来描述的,这些状态变量的取值决定了系统的状态。状态可以是离散的,也可以是连续的。在有限状态机中,状态通常用一个圆圈来表示,圆圈内部写上状态的名称。例如,一个简单的有限状态机可以有两个状态:开和关,它们可以用以下方式表示:

Action(动作)是指在状态转移时执行的操作或动作。当有限状态机从一个状态转移到另一个状态时,可以执行一个或多个action来改变系统的状态或执行某些操作。例如,当有限状态机从“待机”状态转移到“运行”状态时,可以执行一个action来启动系统。在实际应用中,action可以是任何有效的代码,例如函数调用、变量赋值、打印输出等。

FSM 通常用于编程中,用于实现状态转移和控制流程。

注意:

在任何时刻,FSM 只能处于一种状态。

1.2 Go中的FSM

通过上面关于有限状态机的定义,我们大概知道了状态机是个什么东西,那么Golang中是怎么实现的呢。不用慌,已经有大佬实现好了,只管用就好了。

安装:

go get github.com/looplab/fsm@v1.0.1

接下来一起看看github.com/looplab/fsm 是如何使用的。

2、github.com/looplab/fsm 如何使用

注意:

不同版本的 fsm 使用方式,可能不太一样,最好是看下 NewFSM 函数的注释,看下具体的细节。 本篇文章以:github.com/looplab/fsm@v1.0.1 为例。

2.1 fsm 基础使用

这里把官方的例子改了下,感觉官方的例子不是很清晰。代码如下:

package main

import (
	"context"
	"fmt"

	"github.com/looplab/fsm"
)

type Door struct {
	Name  string
	FSM *fsm.FSM
}

func NewDoor(name string) *Door {
	d := &Door{
		Name: name,
	}

	d.FSM = fsm.NewFSM(
		"closed",
		fsm.Events{
			{Name: "open", Src: []string{"closed"}, Dst: "open"},
			{Name: "close", Src: []string{"open"}, Dst: "closed"},
		},
		fsm.Callbacks{
			"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
		},
	)

	return d
}

func (d *Door) enterState(e *fsm.Event) {
	fmt.Printf("The door's name:%s , current state:%s\n", d.Name, e.Dst)
}

func main() {
	door := NewDoor("测试")

	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err := door.FSM.Event(context.Background(), "open")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err = door.FSM.Event(context.Background(), "close")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

执行结果:

fsm current state: closed 
The door's name:测试 , current state:open
fsm current state: open 
The door's name:测试 , current state:closed
fsm current state: closed

这里就通过Event改变FSM中的状态。转移公式为:Src,Event -> Dst,d.enterState。大意就是接受到了输入Event,状态机的StateSrc->Dst,并且执行了Action:d.enterState。

2.2 fsm 中 Action 何时执行?

刚开始使用的时候,好奇d.enterState(e)是什么时候调用的,我们一起看看 NewFSM 中的注释就清楚了。

// NewFSM constructs a FSM from events and callbacks.
//
// The events and transitions are specified as a slice of Event structs
// specified as Events. Each Event is mapped to one or more internal
// transitions from Event.Src to Event.Dst.
// Callbacks are added as a map specified as Callbacks where the key is parsed
// as the callback event as follows, and called in the same order:
//
// 1. before_<EVENT> - called before event named <EVENT>
//
// 2. before_event - called before all events
//
// 3. leave_<OLD_STATE> - called before leaving <OLD_STATE>
//
// 4. leave_state - called before leaving all states
//
// 5. enter_<NEW_STATE> - called after entering <NEW_STATE>
//
// 6. enter_state - called after entering all states
//
// 7. after_<EVENT> - called after event named <EVENT>
//
// 8. after_event - called after all events
//
// There are also two short form versions for the most commonly used callbacks.
// They are simply the name of the event or state:
//
// 1. <NEW_STATE> - called after entering <NEW_STATE>
//
// 2. <EVENT> - called after event named <EVENT>
//
// If both a shorthand version and a full version is specified it is undefined
// which version of the callback will end up in the internal map. This is due
// to the pseudo random nature of Go maps. No checking for multiple keys is
// currently performed.

从上面我们知道了,d.enterState(e) 是在called after entering all states 时执行的。

2.2.1 完整版书写的Callbacks执行顺序

从上面的注释能知道完整版书写的Callbacks的执行顺序如下:

2.2.2 简写版的Callbacks执行顺序

2.2.3 注意事项

虽然Callbacks的写法有两种,但是不能同时使用完整版和简写版,否则最终使用那个版本是不确定的。

2.3 较为完整的例子

package main

import (
	"context"
	"fmt"

	"github.com/looplab/fsm"
)

type Door struct {
	Name  string
	FSM *fsm.FSM
}

func NewDoor(name string) *Door {
	d := &Door{
		Name: name,
	}

	d.FSM = fsm.NewFSM(
		"closed",
		fsm.Events{
			{Name: "open", Src: []string{"closed"}, Dst: "open"},
			{Name: "close", Src: []string{"open"}, Dst: "closed"},
		},
		fsm.Callbacks{
			"before_open": func(_ context.Context, e *fsm.Event) { d.beforeOpen(e) },
			"before_event": func(_ context.Context, e *fsm.Event) { d.beforeEvent(e) },
			"leave_closed": func(_ context.Context, e *fsm.Event) { d.leaveClosed(e) },
			"leave_state": func(_ context.Context, e *fsm.Event) { d.leaveState(e) },
			"enter_open": func(_ context.Context, e *fsm.Event) { d.enterOpen(e) },
			"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
			"after_open": func(_ context.Context, e *fsm.Event) { d.afterOpen(e) },
			"after_event": func(_ context.Context, e *fsm.Event) { d.afterEvent(e) },
		},
	)

	return d
}

func (d *Door) beforeOpen(e *fsm.Event) {
	fmt.Printf("beforeOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) beforeEvent(e *fsm.Event) {
	fmt.Printf("beforeEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) leaveClosed(e *fsm.Event) {
	fmt.Printf("leaveClosed, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) leaveState(e *fsm.Event) {
	fmt.Printf("leaveState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) enterOpen(e *fsm.Event) {
	fmt.Printf("enterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) enterState(e *fsm.Event) {
	fmt.Printf("enterState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) afterOpen(e *fsm.Event) {
	fmt.Printf("afterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) afterEvent(e *fsm.Event) {
	fmt.Printf("afterEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}



func main() {
	door := NewDoor("测试")

	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err := door.FSM.Event(context.Background(), "open")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err = door.FSM.Event(context.Background(), "close")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

执行结果:大家重点看current state何时发生的变化。 

fsm current state: closed 
beforeOpen, current state:closed, Dst:open 
beforeEvent, current state:closed, Dst:open 
leaveClosed, current state:closed, Dst:open 
leaveState, current state:closed, Dst:open 
enterOpen, current state:open, Dst:open 
enterState, current state:open, Dst:open 
afterOpen, current state:open, Dst:open 
afterEvent, current state:open, Dst:open 
fsm current state: open 
beforeEvent, current state:open, Dst:closed 
leaveState, current state:open, Dst:closed 
enterState, current state:closed, Dst:closed 
afterEvent, current state:closed, Dst:closed 
fsm current state: closed 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

认识Spring框架

目录 1.了解Spring框架 2.了解Spring的体系结构 3.认识Spring家族 4.实现第一个Spring入门程序 1.了解Spring框架 1.什么是Spring框架&#xff1f; Spring是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面的容器框架。 关键词概念解释&#xff1a; 1.轻量级…

2023国赛数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

2028量产?兰博基尼首款纯电车型Lanzador亮相,双电机四驱跨界GT

经过多次预热之后&#xff0c;兰博基尼的首款纯电车型Lanzador终于在8月19日正式亮相。这款车以较完整的面貌出现在大众面前&#xff0c;将于2028年开始正式量产。虽然Lanzador仍是一个暂定名字&#xff0c;但它来自西班牙语&#xff0c;意为“投手”、“发射器”和“推动者”&…

C++笔记之注册的含义

C笔记之注册的含义 code review! 文章目录 C笔记之注册的含义1.注册对象到Qt的信号槽系统中2.注册函数到Qt的元对象系统中元对象系统例1例2 3.注册自定义类型到C STL容器中4.将函数指针传递给另一个类&#xff0c;注册回调函数class ICallback存在的意义例1&#xff0c;用于说…

专业课只考2门,计算机学硕最低分290的江苏院校

南京工业大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、专业目录、23复试详情、各专业考情分析。 正文1332字&#xff0c;预计阅读&#xff1a;3分钟。 2023考情概况 南京工业大学计算机相关各专业复试和…

Intellij中直接运行ts配置:run configuration for typescript

在Intellij中可以借助插件run configuration for typescript直接运行typescript&#xff1a; run configuration for typescript插件本质还是依赖于 ts-node 来运行&#xff0c;只是其可以帮助我们自动配置好 ts-node 运行参数&#xff0c;简化使用。 第一步&#xff1a;安装…

uniapp配置添加阿里巴巴图标icon流程步骤

文章目录 下载复制文件到项目文件夹里项目配置目录结构显示图标 下载 阿里巴巴icon官网 https://www.iconfont.cn/ 复制文件到项目文件夹里 项目配置目录结构 显示图标

带你了解建堆的时间复杂度

目录 用向上调整建堆的时间复杂度 1.向上调整建堆的时间复杂度O(N*logN) 2.数学论证 3.相关代码 用向下调整建堆的时间复杂度 1.建堆的时间复杂度为O(N) 2.数学论证 3.相关代码 完结撒花✿✿ヽ(▽)ノ✿✿ 博主建议:面试的时候可能会被面试官问到建堆时间复杂度的证明过…

安卓手机的充电原理

安卓手机的充电原理是通过充电器将交流电转换为直流电&#xff0c;然后通过USB接口传输到手机电池中。手机电池的充电过程分为三个阶段&#xff1a;涓流充电、恒流充电和恒压充电。 充电动画 涓流充电是用来先对完全放电的电池单元进行预充&#xff08;恢复性充电&#xff09…

pdf 转 word

pdf 转 word 一、思路 直接调用LibreOffice 命令进行文档转换的命令行工具 使用的前系统中必须已经安装了 libreofficelibreoffice已翻译的用户界面语言包: 中文 (简体)libreoffice离线帮助文档: 中文 (简体)上传字体 重点&#xff1a;重点&#xff1a;重点&#xff1a; 亲…

蓝光眼镜有效吗?科研团队:无法证明防蓝光镜片可以减少视力伤害

8 月 19 日消息&#xff0c;本次由墨尔本大学、莫纳什大学和伦敦城市大学联合进行的科研团队&#xff0c;对来自 6个国家和地区的 17 项已发表的研究进行了深入研究。他们的研究发现&#xff0c;无法证明防蓝光镜片能够减少眼睛的视力伤害或改善佩戴者的睡眠质量等功效。 这项研…

【网络教程】如何获取阿里云盘的refresh_token

文章目录 获取阿里云盘的refresh_token 获取阿里云盘的refresh_token 这里在Edge浏览器上进行演示首先我们需要登入我们的阿里云盘然后按F12进入开发者模式&#xff0c;在菜单栏选择应用程序&#xff0c;然后在左边菜单找到 本地存储 下的 https://www.aliyundrive.com 这个域…

深入理解【二叉树】

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…

开发过程中自己遇到的异常(四)

mysql 报错&#xff1a;‘Lost connection to MySQL server during query 出现这种情况大多是因为&#xff0c;两个事物抢一个表的使用权造成的。 show processlist; 观察Command 列&#xff0c;有明显的update&#xff0c;insert, delete 时间比较久的&#xff0c;直接kill掉…

unity 之Transform组件(汇总)

文章目录 理论指导结合例子 理论指导 当在Unity中处理3D场景中的游戏对象时&#xff0c;Transform 组件是至关重要的组件之一。它管理了游戏对象的位置、旋转和缩放&#xff0c;并提供了许多方法来操纵和操作这些属性。以下是关于Transform 组件的详细介绍&#xff1a; 位置&a…

微服务系列文章之 SpringBoot 最佳实践

Spring Boot 是一种广泛使用且非常流行的企业级高性能框架。 以下是一些最佳实践和一些技巧&#xff0c;我们可以使用它们来改进 Spring Boot 应用程序并使其更加高效。 Spring Boot 的四大核心 1、自动配置 针对很多Spring应用程序和常见的应用功能&#xff0c;Spring Boo…

Linux 线程的概念与实现方式

一、线程的概念 线程是进程内部的一条执行序列或执行路径&#xff0c;一个进程可以包含多条线程。 一个进程中有两条或多条执行路径的时候&#xff0c;它们是可以同时执行的&#xff0c;也就是说&#xff0c;一个进程中的多个线程是可以同时执行的。当我们需要一个程序同时执行…

【LeetCode-简单题】49. 字母异位词分组

题目 题解一:排序哈希表 思路:由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符串分别进行排序之后得到的字符串一定是相同的&#xff0c;故可以将排序之后的字符串作为哈希表的键。 核心api: //将字符串转换为字符数组char[] ch str.toCharArray();…

飞机打方块(二)游戏界面制作

一、背景 1.新建bg节点 二、飞机节点功能实现 1.移动 1.新建plane节点 2.新建脚本GameController.ts,并绑定Canvas GameControll.ts const { ccclass, property } cc._decorator;ccclass export default class NewClass extends cc.Component {property(cc.Node)canvas:…

详解strcmp函数

strcmp函数是用来比较两个字符串的&#xff0c;按理来说&#xff0c;比较结果只有两种&#xff1a;相同或不同。但是&#xff0c;事实上&#xff0c;strcmp函数在设计时会有三种情况&#xff0c;下面详细介绍&#xff1a; 这个函数的输入为两个字符串的首元素地址&#xff08;即…