状态机
本文参考链接:
层次状态机:https://blog.csdn.net/wuhenyouyuyouyu/article/details/53407936
有限状态机FSM和层次状态机HSM:https://www.cnblogs.com/Pual623548198/p/7064630.html
有限状态机(FSM)、层次状态机(HSM)和行为树(BT)的区别:http://www.cnblogs.com/jeason1997/p/5140201.html
有限状态机实现:https://www.jianshu.com/p/d48e0d565618
=====================================================
一、FSM与常见的几种FSM实现模式
简单来说,有限状态机表示的是系统根据不同输入/不同条件在各个状态之间进行跳转的模型。
可以通过图或表来描述有限状态机,这种图或表一般被称为状态图/状态转移图(State Chart)或状态转移表。因为图更加直观。
由于状态机可以将问题整体的分解成各个部分状态及跳转,直观地对系统进行建模,所以它不仅被用于理论研究过程当中,
而且被广泛用于程序设计实践,在操作系统,网络协议栈,各种分布式应用中都可以见到它的身影。
FSM是对系统的建模,是将问题/解决方案以一种条理化系统化的方式表达出来,映射到人的认知层面,
而要在程序中表达FSM,也需要一定的建模工具,即用某种代码编写的方式(或称之为FSM模式),将FSM以一种条理化系统化的方式映射到代码层面。
这里举个例子用以贯穿整个实现模式
用一个变量state表示当前状态,state可以取两个值S1, S2,输入input表示下一个输入的数字是0还是1
1)嵌套if-else/switch模式
if-else/switch语句已经成为每个程序员手头必备的工具,每当需要”根据不同条件进入不同分支”,就搬出它来组织代码。
这与FSM里面”状态之间根据不同输入进行跳转”的概念有简单的对应关系,这就使得if-else/switch语句成为人们要表达FSM时最先选择的方式。
嵌套if-else/switch具有形式嵌套,代码集中化的特点,它只适合用来表达状态个数少,或者状态间跳转逻辑比较简单的FSM。
嵌套意味着缩进层次的叠加,简单的实现就需要缩进多层,如果状态间的逻辑变得复杂,所需要的缩进不断叠加,代码在水平方向上会发生膨胀;
集中化意味着如果状态个数增多,输入变复杂,代码从垂直方向上会发生指数级别的膨胀。
即使通过简化空分支,抽取逻辑到命名函数等方法来”压缩”水平/垂直方向上的代码行数,依然无法从根本上解决膨胀问题。
代码膨胀后造成可读性和可写性的急剧下降。
2)状态表
另一个比较流行的模式是状态表模式。状态表模式是指将所有的状态和跳转逻辑规划成一个表格来表达FSM。
State\Input | Zero | One |
---|---|---|
S1 | DoSomething, S2 | null |
S2 | DoSomething, S1 | null |
对应S1行, Zero列的”DoSomething, S2”表示当处于状态S1时,如果遇到输入为Zero,那么就执行动作DoSomething,然后跳转到状态S2。
由于例子状态图非常简单,DoSomething动作为空,这里将它特别的列出来只是为了说明在更一般化的情况下如果有其它逻辑可以放到这里来。
用这种方式实现出来的代码跟画出来的状态表有一个直观的映射关系,它要求程序员将状态的划分和跳转逻辑细分到一定的合适大小的粒度,
事件驱动的过程查找是对状态表的直接下标索引,性能也很高。
状态表的大小是不同状态数量S和不同输入数量I的一个乘积 S * I,在常见的场景中,这张状态表可能十分大,占用大量的内存空间,
然而中间包含的有效状态跳转项却相对少,也就是说状态表是一个稀疏的表。
3)状态模式
在OOP的设计模式中,有一个状态模式可以用于表达状态机。状态模式基于OOP中的代理和多态。
父类定义一系列通用的接口来处理输入事件,做为状态机的对外接口形态。
每个包含具体逻辑的子类各表示状态机里面的一个状态,实现父类定义好的事件处理接口。
然后定义一个指向具体子类对象的变量标记当前的状态,在一个上下文相关的环境中执行此变量对应的事件处理方法,来表达状态机。
状态模式将各个状态的逻辑局部化到每个状态类,事件分发和状态跳转的性能也很高,内存使用上也相当高效,没有稀疏表浪费内存的问题。
它将状态和事件通过接口继承分隔开,实现的时候不需要列举所有事件,添加状态也只是添加子类实现,
但要求有一个context类来管理上下文及所有相关的变量,状态类与context类之间的访问多了一个间接层,
在某些语言里面可能会遇到封装问题(比如在C++里面访问private字段要使用friend关键字)。
4)优化的FSM实现
结合上述几种FSM实现模式,我们可以得到一个优化的FSM实现模式,
它用对象方法表示状态,将状态表嵌套到每个状态方法中,
因此它包含了上述几种模式的优点:事件和状态的分离,高效的状态跳转和内存使用,直接的变量访问,直观而且扩展方便。
===================================================
二、HSM
上述几种模式中,状态之间都是相互独立的,状态图没有重合的部分,整个状态机都是平坦的。
然而实际上对很多问题的状态机模型都不会是那么简单,有可能问题域本身就有状态嵌套的概念,
有时为了重用大段的处理逻辑或代码,我们也需要支持嵌套的状态。
这方面一个经典的例子就是图形应用程序的编写,通过图形应用程序的框架(如MFC, GTK, Qt)编写应用程序,
程序员只需要注册少数感兴趣的事件响应,如点击某个按钮,大部分其它的事件响应都由默认框架处理,如程序的关闭。
用状态机来建模,框架就是父状态,而应用程序就是子状态,子状态只需要处理它感兴趣的少数事件,大部分事件都由向上传递到框架这个父状态来处理。
这种事件向父层传递,子层继承了父类行为的结构,我们将其称为 行为继承 ,以区别开OOP里面的 类继承 。
并把这种包含嵌套状态的状态机称为 HSM(hierarchical state machine) ,层次状态机。
=====================================================