文章目录
一:简介
1. 定义
有限状态机(Finite-state machine, FSM)
,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
2. 组成要素
- 现态
(src state)
:事务当前所处的状态。 - 事件
(event)
:事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。 - 动作
(action)
:事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。 - 次态
(dst state)
:事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。 - 状态流转
(transition)
:事物从现态转为次态的整个过程。
注:在任何时刻,FSM 只能处于一种状态。
举例:
上图有三种状态
:绿,黄,红;
- 状态转换是受限的,绿只能转黄,黄只能转红,红只能转绿;诸如黄转绿这样的状态转换是不允许的;
- 状态转换的输入条件很简单,接收到 1(
事件
) 就转换到下一个状态。
简单来说,有限状态机就是一台预先定义好了各种状态的一组状态的机器,当机器接收到一个指令之后就根据指令内容查一张预先定义好的表:
- 检查当前状态是否符合预期,即处于当前状态的机器是否接收具体指令
- 如果不接受,比如红灯状态接受到了2,那么什么都不会发生
- 如果接受,再检查表中“当前状态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