括号里面是本文的副标题,跟着作者本来是想搞明白QP是如何实现有限状态机的,结果万万没想到,其实作者的目的不纯,他不光想让搞明白QP的前世,还想让你的内功心法(C语言)在实战中进阶(好文不多,建议大家读一下原著,或许才能体会到作者的良苦用心,大部分书籍只是教你招式,让你感悟心法的并不多,感谢作者)。
下面还是由一个书中提到的定时炸弹的例子开启我们的侃大山模式,话说一个定时炸弹都有哪些功能,你能想到的:
1.他得能倒计时,时间到,一声 bomb~!!!
2.要倒计时 ,得有个定时时间 ,timer。
3.有了定时时间,得支持定时功能吧 ,得有UP/DOWM按键进行timer调整。
4.有了定时模式,你得切换到倒计时模式吧,这里有ARM按键,按下开启倒计时。
5.我还想让这个炸弹有意思一点,给人一定的机会,提供一个炸弹解除密码吧(友情提示,剪线直接炸给你看~!)
6.设置一个初始密码,你可以通过上下按键进行移位输入密码,输入完成按ARM键,这个时候如果输入正确,那么炸弹解除倒计时,进入定时模式(危险解除),如果输入错误,那么也不会立即爆炸 ,可以重新尝试,毕竟猜对密码难嘛。
7.有了这一些内容,你还需要以一块显示屏,管理倒计时和输入密码的显示,基本就这些了,炸弹的原型就出来了:
如何构建状态机,一千个人有一千种状态机设计思路(可能),并没有标准答案,我总结了一个通用的黄金规则,简单的情况可以copy规则,直接生成状态机,复杂的情况还是需要认真的思考,不断地改进模型,打磨出更完美的设计,如下:
1. 一个项目中需要创建多个状态机,状态机的本质是管理资源,什么是资源,内存 IO 键盘、显示器等等,也可以是虚拟的资源,例如状态机扩展变量,当然一个状态机不一定只能管理一种资源,但当你不知道该怎么组合的时候,那么就一个状态机,一个资源,状态机会多一些,但没什么问题。
2.一个状态机会在多种状态之间进行切换,那么如何定义到底有几个状态,很简单,这取决于对事件的反应不同,有几种不同的动作就构造几个状态,相同的动作就放在一个父状态里(HSM中)。
这个状态机很简单,只需要创建一个状态机(bomb),两个状态setting和timming状态,再加一个伪状态(炸了状态)就实现了,状态图如下:
基于这个项目,我们提供三个版本的实现让我们的C彻底进阶:
初级版:
1. 先定义状态机主体和状态定义:
2. 定义事件主体和事件定义:
3.一个状态机还需要三个函数来帮助他实现自身的功能,构造函数,初始化函数和事件分发处理函数,声明和定义如下:
4.接下来我们来写一下main.c的核心部分,如下:
到这里整个方案的代码的核心部分就全了,这里你可以思考一下这么写的优点和缺点,想想如何改进,想10秒再往下看(答案固然重要,独立思考更重要),倒计时:
10
想一想优点
9
想一想缺点,别急
8
假如是你,你该如何改进它
7
好了,揭晓我的答案(不一定准确)
654321~!
总结,先说说这么写的优点:
1.符合我们对于事物的思考方式,你可以很快掌握他是符合运行,没有太大的难度。
2.他并没有用太多的复杂的设计方法,例如使用指针,复杂的类型变化,算法优化,不需要工程师太深的基础就能掌握。
3.基于他的设计方式,你可以很快上手,来改进自己的项目,让他看起来更加简洁明了。
接下来我们来说说他的缺点:
1.首先我们看到void Bomb1_dispatch(Bomb1 *me, Event const *e)这个函数的实现有点长,我们知道软件的设计的规则中,有一条让你写的函数尽可能的短小,简单明了,这里只有两个状态,三个事件就已经占了这么大的篇幅了,再加几个还得了~!
2.第二点,这并不是一个很好的构架,因为不论以后你怎么扩展这个状态机,你的主战场都是盯着这个dispatch函数,哪怕你可以把处理封装成函数,让它调用,但是它就好像是树根一样,每长一片叶子,这个树干都要做一些处理,这并不是一个很好的构架,从事多年程序设计的人员应该深有体会,慢慢的他会随着需求变得不再简单明了,远离我们的初衷。
片花:我们的初衷,dispatch不应该是这样,他只是需要传入一个状态机和事件,然后把控制权转交给这个状态机,等待处理完以后,控制权再回到dispatch函数中,这里可能会涉及一个课题,如何把状态机从dispatch中剥离出来,所谓的剥离并不是完全独立,而是让所有的状态机可以拥有一个dispatch,而不需要植入任何和自己相关的状态机代码,思想上升,如何设计一个通用的dispatch(事件处理器,简称QEP,先引出核心课题),下一篇我们开启进阶之路2。