1、状态机的要素
状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
这里需要注意的两个问题:
1、避免把某个“程序动作”当作是一种“状态”来处理。那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
2、状态划分时漏掉一些状态,导致跳转逻辑不完整。
所以维护上述一张状态表就非常必要,而且有意义了。从表中可以直观看出那些状态直接存在跳转路径,那些状态直接不存在。如果不存在,就把对应的单元格置灰。每次写代码之前先把表格填写好,并且对置灰的部分重点review,看看是否有“漏态”,然后才是写代码。QA拿到这张表格之后,写测试用例也是手到擒来。
思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的事务。
有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_state),决定执行的动作(action),并设置下一个状态号(nxt_state)。
-------------
| |-------->执行动作action
发生事件event ----->| cur_state |
| |-------->设置下一状态号nxt_state
-------------
当前状态
图1 有限状态机工作原理
e0/a0
--->--
| |
-------->----------
e0/a0 | | S0 |-----
| -<------------ | e1/a1
| | e2/a2 V
---------- ----------
| S2 |-----<-----| S1 |
---------- e2/a2 ----------
图2 一个有限状态机实例
--------------------------------------------
当前状态 s0 s1 s2 | 事件
--------------------------------------------
a0/s0 -- a0/s0 | e0
--------------------------------------------
a1/s1 -- -- | e1
--------------------------------------------
a2/s2 a2/s2 -- | e2
--------------------------------------------
表1 图2状态机实例的二维表格表示(动作/下一状态)
图2为一个状态机实例的状态转移图,它的含义是:
在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变;
如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;
如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;
有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示 的含义是完全相同的。
观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写(在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然 不同。
cur_state = nxt_state;
switch(cur_state){ //在当前状态中判断事件
case s0: //在s0状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并保持状态不变;
执行a0动作;
//nxt_state = s0; //因为状态号是自身,所以可以删除此句
,以提高运行速度。
}
else if(e1_event){ //如果发生e1事件,那么就执行a1动作,
并将状态转移到s1态;
执行a1动作;
nxt_state = s1;
}
else if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s1: //在s1状态
if(e2_event){ //如果发生e2事件,那么就执行a2动作,
并将状态转移到s2态;
执行a2动作;
nxt_state = s2;
}
break;
case s2: //在s2状态
if(e0_event){ //如果发生e0事件,那么就执行a0动作,
并将状态转移到s0态;
执行a0动作;
nxt_state = s0;
}
}
横着写(在事件中判断状态)C代码片段
//e0事件发生时,执行的函数
void e0_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e0事件发生时,s1处为空
case s2:
执行a0动作;
*nxt_state = s0;
}
}
//e1事件发生时,执行的函数
void e1_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e1事件发生时,s1和s2处为
空
执行a1动作;
*nxt_state = s1;
}
}
//e2事件发生时,执行的函数
void e2_event_function(int * nxt_state)
{
int cur_state;
cur_state = *nxt_state;
switch(cur_state){
case s0: //观察表1,在e2事件发生时,s2处为空
case s1:
执行a2动作;
*nxt_state = s2;
}
}
评论(0)