Go fsm有限状态机

366 阅读2分钟

简介

在高质量编码的理念中,需要把控制逻辑和业务逻辑进行区分,这样的实现代码才更清晰,也更好维护;fsm有限状态机是其中一个很好的案例,这里介绍下go语言中的使用示例,详情可参考github.com/looplab/fsm

示例

源码如下:

package main

import (
	"context"
	"fmt"

	"github.com/looplab/fsm"
)

// Door 定义一个结构体类型
type Door struct {
	Name string
	FSM  *fsm.FSM
}

// NewDoor 初始化结构体以及状态机
func NewDoor(name string) *Door {
	d := &Door{
		Name: name,
	}

	d.FSM = fsm.NewFSM(
		// 初始状态
		"closed",
		// 有限状态机
		fsm.Events{
			// 事件名称、源状态,目的状态
			{Name: "OPEN", Src: []string{"closed"}, Dst: "opened"},
			{Name: "CLOSE", Src: []string{"opened"}, Dst: "closed"},
		},
		// 执行函数(动作)
		// 支持具体的状态和事件before_<EVENT>、leave_<OLD_STATE>、enter_<NEW_STATE>、after_<EVENT>
		fsm.Callbacks{
			"before_event": func(_ context.Context, e *fsm.Event) { d.beforeEvent(e) },
			"after_event":  func(_ context.Context, e *fsm.Event) { d.afterEvent(e) },
			"enter_state":  func(_ context.Context, e *fsm.Event) { d.enterState(e) },
			"leave_state":  func(_ context.Context, e *fsm.Event) { d.leaveState(e) },
		},
	)

	return d
}

// 定义before_event的回调函数,即事件触发前会调用回调函数
func (d *Door) beforeEvent(e *fsm.Event) {
	fmt.Printf("[before_event]The door name[%s] is from %s to %s\n", d.Name, e.Src, e.Dst)
}

// 定义after_event的回调函数,即事件触发后会调用回调函数
func (d *Door) afterEvent(e *fsm.Event) {
	fmt.Printf("[after_event]The door name[%s] is from %s to %s\n", d.Name, e.Src, e.Dst)
}

// 定义enter_state的回调函数,即进入一个状态时会调用回调函数
func (d *Door) enterState(e *fsm.Event) {
	fmt.Printf("[enter_state]The door name[%s] is from %s to %s\n", d.Name, e.Src, e.Dst)
}

// 定义leave_state的回调函数,即离开一个状态时会调用回调函数
func (d *Door) leaveState(e *fsm.Event) {
	fmt.Printf("[leave_state]The door name[%s] is from %s to %s\n", d.Name, e.Src, e.Dst)
}

func main() {
	door := NewDoor("mydoor")

	fmt.Println("==>OPEN the door!")
	// 触发OPEN事件
	err := door.FSM.Event(context.Background(), "OPEN")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("==>OPEN the door again!")
	// 触发OPEN事件
	err = door.FSM.Event(context.Background(), "OPEN")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("==>CLOSE the door!")
	// 触发CLOSE事件
	err = door.FSM.Event(context.Background(), "CLOSE")
	if err != nil {
		fmt.Println(err)
	}
}

运行结果如下:

[xiaofeng@golang-dev gofsm]$ go run main.go 
==>OPEN the door!
[before_event]The door name[mydoor] is from closed to opened
[leave_state]The door name[mydoor] is from closed to opened
[enter_state]The door name[mydoor] is from closed to opened
[after_event]The door name[mydoor] is from closed to opened
==>OPEN the door again!
event OPEN inappropriate in current state opened
==>CLOSE the door!
[before_event]The door name[mydoor] is from opened to closed
[leave_state]The door name[mydoor] is from opened to closed
[enter_state]The door name[mydoor] is from opened to closed
[after_event]The door name[mydoor] is from opened to closed
[xiaofeng@golang-dev gofsm]$ 

总结

fsm有限状态机核心三要素:事件、状态、动作,可以按如下思路进行实现

  1. 枚举出所有可能的状态。
  2. 枚举出所有可能的事件。
  3. 事件和状态的对应关系,即触发一个事件后,状态会从什么变成什么。
  4. 补充动作,即在某些事件发生或状态变更的前后需要执行的动作。
  5. 优点是控制逻辑一开始定义好,业务逻辑都封装在执行函数,实现很清晰。