使用状态机的好处:
1. 有限状态机(FSM)是收敛的,保证用户操作(外部事件)都能够响应,并且回到可以预测的可控制范围内。
2. 状态机是正交的,可以覆盖用户操作(外部事件)的所有可能性,而又不会重复处理。
3. 另外,状态机作为一个通用的表现形式,有时候还能够在某一个抽象层次上作为一个可以复用的模板。比如高通的CDMA代码中,实现ICMP协议和LCP协议的时候就抽象出了一个通用的FSM。
既然状态机有这样的好处,为什么不是所有的程序都用状态机的形势表示和处理呢?这是因为“有限”两个字,如果都用状态机表示,势必造成状态的无限扩展,反尔不好处理。比如,在输入密码的界面,如果每输入一个密码,也认为是一个新的状态,那么……
另外,状态机表现世界也不是万能的,比如状态机不具有记忆性(仅能记忆当前的内容),所以,需要配合其它的结构,比如栈等。比如,在brew程序里,使用栈来记忆App获得焦点的顺序。
怎样比较好的实现一个状态机:
状态机的一般表现形式:
event / action
A -------------------------> B
action 又可以分为:
1) 接收到event时做的事情,这个时候还不发生状态的改变, s_ev_handle;
2) 状态改变时,从A退出做的动作, exit_action;
3) 状态改变时,进入B做的动作, enter_action。
这样的分法有这样的好处:
1)有的时候,一个状态在等到某一个事件时,会进行一次长时间的,或者是特定的处理,或者处理完成并不进行状态的转移,这个时候用s_ev_handle进行特别处理比较合理。
2) 有的时候,进入或退出一个状态都会进行一些公共的处理,例如初始化或者回收状态期间的临时对象等,比如,在mot的UI程序中,创建或POP该状态对应的FORM;注册某些特定状态下才会响应或不响应的事件等,这个时候用enter_action或exit_action比较合理。
3) 状态转移应该是个比较快的过程,这样才能满足时间上状态机的正交性,所以enter_action和exit_action应该处理一些耗时比较短的动作。
4) 通过在状态初始化时进行enter_action的动作,状态中进行s_ev_handle的处理,状态退出时进行exit_action的动作,一个状态就有了自己的生命周期,这个在C编程中似乎只是形式上的东西,没有什么意义,但是通过这种OO的思考方式应该对设计有所帮助。
下表是一个状态机的通用表现形式
ev1 | ev2 | ev3 | |
s1 (enter_action, exit_action) | s1_ev1_handle | ||
s2 | |||
s3 |
在mot目前的代码框架中,brew_comm就是通过遍历每个App的状态表实现的,brew_comm的event_handle函数的第一次循环是按行遍历了状态表的第一列,找到了当前的App状态,再按列遍历找到event响应的处理函数。
另外,brew_comm还提供了app_change_state的方法,实现状态转移时enter_action, exit_action的调用。
运用状态机有可能遇到的问题:
有的时候,会发现enter_action的动作中需要上一个状态的信息,比如按CLR键进入前一个FORM时有可能需要知道是从哪个FORM退回的,那是不是要把前一个FORM的enter_action的动作加到s_CLR_handle中去呢?我觉得这里有两个思路可以解决这个问题,1) 利用别的数据结构来克服状态机缺乏记忆功能的问题,例如使用一个变量保存s_CLR_handle的状态标识;
2) 索性把新状态分成几个状态(或者子状态),因为从状态机描述客观世界的角度看,处理流程的不一致,其实就是产生了新的状态。不过这个方法好像和目前mot的框架思想不太一致,因为这样可能会产生这样的结果:一个FORM会对应若干个状态。
一点建议:
在设计到编写代码之前,建议填写完整上面的状态表格,能够集中检查设计中的不足;统一确定各个事件,状态,处理函数的命名规则,使程序具有较好的可维护性;能够利用第三方工具直接生成代码,进行测试,或者反映射成状态图等。
需不需要在一个App进入和退出的时候加两个不需要FORM对应的伪状态?这样可以解决下面一些问题:
1)比如在进入App的第一个状态前,App需要根据一个事件才能确定进入哪个状态,如果加了初始的伪状态,App就可以在伪状态中等待触发事件了。
2) App可以从很多状态退出,那么这些状态都需要调用App的退出处理过程,这时,加一个结束伪状态更加可以精简代码,并且提高状态机的收敛性和完备性。