有限状态机FSM

简介: 关于状态机,以前写过[用Go实现一个状态机](https://mp.weixin.qq.com/s?__biz=MzUzNzAzMTc3MA==&mid=2247484850&idx=1&sn=5ba31ff066ddeeedab27f9ca9f1b9b58&scene=21#wechat_redirect),只是讲述了如何控制状态的流转,理论上不能算作完整的状态机。

关于状态机,以前写过用Go实现一个状态机,只是讲述了如何控制状态的流转,理论上不能算作完整的状态机。

一个完整的状态机其过程如下:发生一个event(事件)后,根据当前存在的状态(cur-state),决定执行的“动作”(action),并设置下一个状态号(transition)。

其中:

  1. 事件(Event)指的是在时间和空间上占有一定位置,并且对状态机来讲是有意义的那些事情。事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。
  2. 状态(State)指的是对象在其生命周期中的一种状况,处于某个特定状态中的对象必然会满足某些条件、执行某些动作或者是等待某些事件。
  3. 转换(Transition)指的是两个状态之间的一种关系,表明对象将在第一个状态中执行一定的动作,并将在某个事件发生同时某个特定条件满足时进入第二个状态。

这次我们看一下有限状态机及其实战。

FSM

定义

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

FSM可以把模型的多状态、多状态间的转换条件解耦。可以使维护变得容易,代码也更加具有可读性,也更加艺术。

源码

样例

github上https://github.com/looplab/fsm ,有1.8K Star,我们以这个开源项目为例,讲一下FSM的具体实现。此处用开门、关门为例,状态虽然很,但能很好的说明问题。

样例代码位置为:https://github.com/shidawuhen/asap/blob/master/controller/various/fsm.go

package main

import (
    "fmt"
    "github.com/looplab/fsm"
)

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

func NewDoor(to string) *Door {
    d := &Door{
        To: to,
    }

    d.FSM = fsm.NewFSM(
        "closed",
        fsm.Events{
            {Name: "open", Src: []string{"closed"}, Dst: "open"},
            {Name: "close", Src: []string{"open"}, Dst: "closed"},
        },
        fsm.Callbacks{
            //指定状态
            "leave_closed": func(e *fsm.Event) { d.leaveClose(e) },
            "before_open":  func(e *fsm.Event) { d.beforeOpen(e) },
            "enter_open":   func(e *fsm.Event) { d.enterOpen(e) },
            "after_open":   func(e *fsm.Event) { d.afterOpen(e) },
            //通用状态
            "enter_state": func(e *fsm.Event) { d.enterState(e) },
        },
    )
    return d
}

func (d *Door) beforeOpen(e *fsm.Event) {
    fmt.Printf("beforeOpen, The door to %s is %s\n", d.To, e.Dst)
}

func (d *Door) enterOpen(e *fsm.Event) {
    fmt.Printf("enterOpen, The door to %s is %s\n", d.To, e.Dst)
}

func (d *Door) afterOpen(e *fsm.Event) {
    fmt.Printf("afterOpen, The door to %s is %s\n", d.To, e.Dst)
}

func (d *Door) leaveOpen(e *fsm.Event) {
    fmt.Printf("leaveOpen, The door to %s is %s\n", d.To, e.Dst)
}

func (d *Door) leaveClose(e *fsm.Event) {
    fmt.Printf("leaveClose, The door to %s is %s\n", d.To, e.Dst)
}

func (d *Door) enterState(e *fsm.Event) {
    fmt.Printf("The door to %s is %s\n", d.To, e.Dst)
}

func main() {
    door := NewDoor("heaven")
    fmt.Println("当前状态为:" + door.FSM.Current())

    err := door.FSM.Event("open")
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("当前状态为:" + door.FSM.Current())
    err = door.FSM.Event("close")
    if err != nil {
        fmt.Println(err)
    }
}

输出:

➜  myproject go run main.go
当前状态为:closed
beforeOpen, The door to heaven is open
leaveClose, The door to heaven is open
enterOpen, The door to heaven is open
The door to heaven is open
afterOpen, The door to heaven is open
当前状态为:open
The door to heaven is closed

说明

  1. NewDoor里的Events存放状态机的信息
  • Name:事件
  • Src:当前状态
  • Dst:目标状态

表示某事件发生时,如果当前为Src状态,可变换为Dst状态

  1. NewDoor里的Callbacks存放转换动作
  • 转换动作可分为通用转换和指定转换,通用转换状态格式为***\_state,指定转换状态格式为***\_状态名
  • 无论通用还是指定转换状态,都是四种,分别对应样例中的代码
  • 进入当前状态前做什么:before\_**
  • 离开上一个状态做什么:leave\_**
  • 进入当前状态做什么:enter\_**
  • 当前状态执行完做什么:after\_**
  • 执行顺序为:https://www.processon.com/view/link/6289e3bd1e08533ae716e7ad

图片

实例


FSM可以用在状态多、变化也多的地方,如履约单。一般订单履约涉及很多状态,而且这些状态经常会变更,使用FSM会方便很多。其中有几个实现要点:

  1. 订单上要记录当前状态
  2. 需要维护状态机,可以从两方面考虑
  • 画图:清晰的资料能帮我们快速了解当前整个状态机的情况
  • 状态机存储:可以将状态机写在代码中或存放到数据库中,格式如Events所示,至少需要有事件、当前状态、目标状态
  1. 转移实现
  • 转移有通用转移和指定转移,好的通用转移逻辑能增强复用性
  • 指定状态可以独立实现,因为转换代码已解耦,对系统影响很小
  1. 状态一致性
  • 先计算出目标状态,当正确完成所有操作后,更新订单状态为目标状态

总结

通过分析源码和实例,大家能够看到使用FSM不但能清晰维护状态机,而且对状态的更改、对转移功能的更改都实现了解耦,大大减少了维护成本。

同时通过合理的设计,赋予研发人员对状态转移操作极大的控制性,可以从离开、进入前、进入、完成后四个时机进行控制。

在友好程度上,我觉得是比Go设计模式(22)-状态模式更好一些的。

资料

  1. 有限状态机FSM详解(一)
  2. FSM学习笔记
  3. 状态机的两种写法
  4. Gofsm
  5. https://github.com/looplab/fsm

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

往期文章回顾:

  1. 设计模式
  2. 招聘
  3. 思考
  4. 存储
  5. 算法系列
  6. 读书笔记
  7. 小工具
  8. 架构
  9. 网络
  10. Go语言
相关文章
|
uml
状态机
首先需要考虑涉及到哪些状态节点和哪些事件,如何方便状态节点的获取、状态节点如何串联起来呢?串联的方式下,如何拿到下一个状态节点?如果基于角色,如何实现? 我们知道工作流可以实现基于角色进行流程的流转,但是此时我们涉及到事件和状态,会出现多个分支,如果使用工作流实现,流程处理上,比如activiti上,可能比较复杂,因此考虑比较轻量级的状态机来实现的话,相对来说要方便一些。
915 0
状态机
|
5月前
|
存储 自然语言处理 JavaScript
用有限状态机实现一个简版的html解析器
理解了状态机就如给你按上了一双翅膀,不管给你任何一段字符任容,都可以通过状态机来拆分成我们想要的结构,理解了上面这些再去看 vue 里的模板编译,你就能知道它到底是怎么加进去那些语法糖的了
69 5
|
5月前
|
人工智能 安全 图形学
有限状态机的概念
有限状态机的概念
|
6月前
|
C++
4 状态机
4 状态机
30 0
|
8月前
|
传感器 数据可视化 JavaScript
状态机(State Machines):理解、设计和应用有限状态机
状态机(State Machines)是一种强大的计算模型和设计工具,用于建模和控制有限状态的系统和行为。无论是在软件开发、自动化控制、游戏设计还是其他领域,状态机都发挥着关键作用。本博客将深入探讨状态机的概念、工作原理以及如何在不同应用中设计和应用它们。
1262 0
|
11月前
|
算法 Linux Android开发
c++状态机的使用
c++状态机的使用
|
存储 算法 异构计算
基于Verilog HDL的状态机描述方法
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
109 0
基于Verilog HDL的状态机描述方法
Verilog语法入门(十一)有限状态机(FSM)
Verilog HDL是一种硬件描述语言(HDL:Hardware Description Language),以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。 Verilog HDL和VHDL是世界上最流行的两种硬件描述语言,都是在20世纪80年代中期开发出来的。前者由Gateway Design Automation公司(该公司于1989年被Cadence公司收购)开发。两种HDL均为IEEE标准。
133 0
|
设计模式 缓存
U3D客户端框架之商业项目中的 FSM 有限状态机 实现代码
FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。