22.有限状态机(一)go语言fsm库

一:简介

1. 定义

有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

2. 组成要素

  • 现态(src state):事务当前所处的状态。
  • 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
  • 状态流转(transition):事物从现态转为次态的整个过程。

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

举例:
在这里插入图片描述

上图有三种状态:绿,黄,红;

  1. 状态转换是受限的,绿只能转黄,黄只能转红,红只能转绿;诸如黄转绿这样的状态转换是不允许的;
  2. 状态转换的输入条件很简单,接收到 1(事件) 就转换到下一个状态。

简单来说,有限状态机就是一台预先定义好了各种状态的一组状态的机器,当机器接收到一个指令之后就根据指令内容查一张预先定义好的表:

  1. 检查当前状态是否符合预期,即处于当前状态的机器是否接收具体指令
  2. 如果不接受,比如红灯状态接受到了2,那么什么都不会发生
  3. 如果接受,再检查表中“当前状态x该指令”对应的目标状态是什么,然后把机器状态转换为目标状态(状态转移

至于何时发送指令给状态机,那是由外部系统决定的,比如红绿灯的例子里,外部系统是几个定时器,时间到了就发信号给有限状态机切换状态。

注:这个红绿灯的例子中接收到事件时只有状态转移,没有动作

3. 优点

  • 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
  • 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
  • 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
  • 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph

二:github.com/looplab/fsm库

1. Go中的FSM

通过上面关于有限状态机的定义,我们大概知道了状态机的相关概念,在Golang中已经有现成的开源包可以使用了。

安装:

go get github.com/looplab/fsm

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

2. 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{  // 事件与状态转移,次态是唯一的,但是能走到次态的源态可以有多个,所以是Src数组
			{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())
}

执行结果:
在这里插入图片描述

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

3. 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 时执行的。

4. 完整版书写的Callbacks执行顺序

从上面的注释能知道完整版书写的Callbacks的执行顺序如下:
在这里插入图片描述

5. 简写版的Callbacks执行顺序

在这里插入图片描述
注:虽然Callbacks的写法有两种,但是不能同时使用完整版和简写版,否则最终使用那个版本是不确定的。

6. 完整示例

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 
// 注意:此时状态发生了变化,由原来的closed变为了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 
// 注意:此时状态发生了变化,由原来的open变为了closed了
enterState, current state:closed, Dst:closed 
afterEvent, current state:closed, Dst:closed 
fsm current state: closed 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
有限状态机FSM)是一个数学模型,用于描述具有有限数量状态的系统的行为。FSM有三个主要组成部分:状态集合、输入集合和状态转换函数。 首先,有限状态机由一组离散的状态组成。状态是系统在特定时间点的情况的表示,可以是一个特定的变量或属性。例如,一个交通信号灯可以有三种状态:红灯、黄灯和绿灯。 其次,有限状态机还包括输入集合,可以触发状态之间的转换。输入可以是外部条件,例如一个按钮的按下、一个传感器的数据或一个特定的事件。例如,在交通信号灯的情况下,按下按钮可能是一个输入,将状态从红灯转换到绿灯。 最后,有限状态机还包括状态转换函数,它指示在给定状态和输入情况下系统应该如何转换到下一个状态。转换可以是确定性的,也可以是非确定性的。在交通信号灯的情况下,状态转换函数可以定义为:当状态是红灯时,如果接收到按钮按下的输入,则将状态转换为绿灯。 通过将这三个组成部分结合起来,我们可以使用有限状态机来描述和计算系统的行为。FSM广泛应用于计算机科学和工程领域,用于模型验证、软件开发、自动控制等领域。 总之,有限状态机是一种简单但强大的数学模型,用于描述具有有限数量状态的系统的行为。它通过状态集合、输入集合和状态转换函数来建模系统,用于解决各种计算和控制问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值