热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

有限状态机FSM的原理与GO的实现

有限状态机(Finite-statemachine,简写FSM)又可以称作有限状态自动机。它必须是可以附着在某种事物上的,且该事物的状态是有限的,通过某些触发事件,会让其状态发生转

《有限状态机FSM的原理与GO的实现》

有限状态机(Finite-state machine, 简写FSM)又可以称作有限状态自动机。它必须是可以附着在某种事物上的,且该事物的状态是有限的,通过某些触发事件,会让其状态发生转换。为此,有限状态机就是描述这些有限的状态和触发事件及转换行为的数学模型。

有限状态机组成

有限状态机有两个必要的特点,一是离散的,二是有限的。基于这两点,现实世界上绝大多数事物因为复杂的状态而无法用有限状态机表示。

而描述事物的有限状态机模型的元素由以下组成:

  • 状态(State):事物的状态,包括初始状态和所有事件触发后的状态
  • 事件(Event):触发状态变化或者保持原状态的事件
  • 行为或转换(Action/Transition):执行状态转换的过程
  • 检测器(Guard):检测某种状态要转换成另一种状态的条件是否满足

应用领域

除了刚刚介绍的数学模型应用,有限状态机在许多不同领域都有重要应用,包括电气工程、语言学、计算机科学、哲学、生物学、数学和逻辑学。有限状态机归属于自动机理论,下面的自动机理论的领域分层图中就可以看出,越是外层的概念越复杂。

《有限状态机FSM的原理与GO的实现》

有限状态机的举例

我们就拿身边最经典的电风扇来举例。假如电风扇有4个按钮,分别是关、1档、2档和3档,关按钮负责关闭电风扇,也就是停止电风扇的转动;而1、2、3档都可以让电风扇开启,且风扇转动的速度不一样,产生的风力也不一样。

这时我们判断出风扇的4个状态,分别是关闭(poweroff)、1档(1st gear)、2档(2nd gear)、3档(3rd gear)。而4个按钮的按下操作可以影响电风扇的状态。下面用状态图来说明:

《有限状态机FSM的原理与GO的实现》

如果看不清楚,还有状态转移表

《有限状态机FSM的原理与GO的实现》

为了更直观的让程序员了解FSM具体有什么用,我将电风扇的有限状态机用程序来演示。

Go语言下的有限状态机

一共2个文件,fsm.go是有限状态机的抽象定义,main.go里是有限状态机在电风扇上的具体状态呈现,代码如下:

// fsm.go
package main
import (
"fmt"
"sync"
)
type FSMState string // 状态
type FSMEvent string // 事件
type FSMHandler func() FSMState // 处理方法,并返回新的状态
// 有限状态机
type FSM struct {
mu sync.Mutex // 排他锁
state FSMState // 当前状态
handlers map[FSMState]map[FSMEvent]FSMHandler // 处理地图集,每一个状态都可以出发有限个事件,执行有限个处理
}
// 获取当前状态
func (f *FSM) getState() FSMState {
return f.state
}
// 设置当前状态
func (f *FSM) setState(newState FSMState) {
f.state = newState
}
// 某状态添加事件处理方法
func (f *FSM) AddHandler(state FSMState, event FSMEvent, handler FSMHandler) *FSM {
if _, ok := f.handlers[state]; !ok {
f.handlers[state] = make(map[FSMEvent]FSMHandler)
}
if _, ok := f.handlers[state][event]; ok {
fmt.Printf("[警告] 状态(%s)事件(%s)已定义过", state, event)
}
f.handlers[state][event] = handler
return f
}
// 事件处理
func (f *FSM) Call(event FSMEvent) FSMState {
f.mu.Lock()
defer f.mu.Unlock()
events := f.handlers[f.getState()]
if events == nil {
return f.getState()
}
if fn, ok := events[event]; ok {
oldState := f.getState()
f.setState(fn())
newState := f.getState()
fmt.Println("状态从 [", oldState, "] 变成 [", newState, "]")
}
return f.getState()
}
// 实例化FSM
func NewFSM(initState FSMState) *FSM {
return &FSM{
state: initState,
handlers: make(map[FSMState]map[FSMEvent]FSMHandler),
}
}

// main.go
package main
import (
"fmt"
)
var (
Poweroff = FSMState("关闭")
FirstGear = FSMState("1档")
SecOndGear= FSMState("2档")
ThirdGear = FSMState("3档")
PowerOffEvent = FSMEvent("按下关闭按钮")
FirstGearEvent = FSMEvent("按下1档按钮")
SecOndGearEvent= FSMEvent("按下2档按钮")
ThirdGearEvent = FSMEvent("按下3档按钮")
PowerOffHandler = FSMHandler(func() FSMState {
fmt.Println("电风扇已关闭")
return Poweroff
})
FirstGearHandler = FSMHandler(func() FSMState {
fmt.Println("电风扇开启1档,微风徐来!")
return FirstGear
})
SecOndGearHandler= FSMHandler(func() FSMState {
fmt.Println("电风扇开启2档,凉飕飕!")
return SecondGear
})
ThirdGearHandler = FSMHandler(func() FSMState {
fmt.Println("电风扇开启3档,发型被吹乱了!")
return ThirdGear
})
)
// 电风扇
type ElectricFan struct {
*FSM
}
// 实例化电风扇
func NewElectricFan(initState FSMState) *ElectricFan {
return &ElectricFan{
FSM: NewFSM(initState),
}
}
// 入口函数
func main() {
efan := NewElectricFan(Poweroff) // 初始状态是关闭的
// 关闭状态
efan.AddHandler(Poweroff, PowerOffEvent, PowerOffHandler)
efan.AddHandler(Poweroff, FirstGearEvent, FirstGearHandler)
efan.AddHandler(Poweroff, SecondGearEvent, SecondGearHandler)
efan.AddHandler(Poweroff, ThirdGearEvent, ThirdGearHandler)
// 1档状态
efan.AddHandler(FirstGear, PowerOffEvent, PowerOffHandler)
efan.AddHandler(FirstGear, FirstGearEvent, FirstGearHandler)
efan.AddHandler(FirstGear, SecondGearEvent, SecondGearHandler)
efan.AddHandler(FirstGear, ThirdGearEvent, ThirdGearHandler)
// 2档状态
efan.AddHandler(SecondGear, PowerOffEvent, PowerOffHandler)
efan.AddHandler(SecondGear, FirstGearEvent, FirstGearHandler)
efan.AddHandler(SecondGear, SecondGearEvent, SecondGearHandler)
efan.AddHandler(SecondGear, ThirdGearEvent, ThirdGearHandler)
// 3档状态
efan.AddHandler(ThirdGear, PowerOffEvent, PowerOffHandler)
efan.AddHandler(ThirdGear, FirstGearEvent, FirstGearHandler)
efan.AddHandler(ThirdGear, SecondGearEvent, SecondGearHandler)
efan.AddHandler(ThirdGear, ThirdGearEvent, ThirdGearHandler)
// 开始测试状态变化
efan.Call(ThirdGearEvent) // 按下3档按钮
efan.Call(FirstGearEvent) // 按下1档按钮
efan.Call(PowerOffEvent) // 按下关闭按钮
efan.Call(SecondGearEvent) // 按下2档按钮
efan.Call(PowerOffEvent) // 按下关闭按钮
}

执行后返回:

电风扇开启3档,发型被吹乱了!
状态从 [ 关闭 ] 变成 [ 3档 ]
电风扇开启1档,微风徐来!
状态从 [ 3档 ] 变成 [ 1档 ]
电风扇已关闭
状态从 [ 1档 ] 变成 [ 关闭 ]
电风扇开启2档,凉飕飕!
状态从 [ 关闭 ] 变成 [ 2档 ]
电风扇已关闭
状态从 [ 2档 ] 变成 [ 关闭 ]

推荐阅读
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
author-avatar
Cockroach小小强
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有