浅谈设计模式之状态机模式

一. 为什么要引入状态机模式?

  1. 系统状态的变化引起实际行为的改变,因此我们需要在系统的主任务中判断他们处于哪一个状态,使用if-else if-else或者switch-case分支结构可以很好的解决。但是如果在某个每个状态在不同场景都有多个分支、并且需要频繁切换状态且状态繁多的系统中,如果仍然使用if-else if-else或者switch-case分支结构,会降低代码的可阅读性以及可维护性。
  2. 引入状态机模式,即是把所有的状态分隔为一层,将每个状态下遇到实际场景的行为分隔为一层,并将所有状态列出到一个表中,当需要增加替换删除某个状态时,直接修改状态表即可。
  3. 当系统遇到事件需要切换状态时,将事件转换为查表序号,然后直接进行查表得到切换后的状态即可,这样一来可以去除大量if-else if-else或者switch-case分支结构。虽然在查找状态的方法上分支模式的switch case和状态机模式的查表法在一定情况下效率是相近的(需要将外部事件进行转换才能得出状态的情况),但是在代码可阅读性以及可维护性上,分支模式是比不上状态机模式的。综上可知引入状态机模式一定程度上解决了代码可读性差和维护性差的问题,如图所示,在阅读和维护代码时的差别。
    浅谈设计模式之状态机模式_第1张图片

二. 状态机模式的实现

  1. 将系统切换状态的过程定义如下:原状态遇到某个事件,需要转换成现状态,现状态根据系统遇到的场景,选择合适的行为,即原状态->事件->转换->现状态->场景->行为。因此定义如下数据结构:
/*状态结构体*/
typedef struct _state
{
    void (*State_Prepare)(void);	//转换过程
    void (*State_Process)(void);	//状态处理
    void (*Behavior_Process)(void); //执行行为
}State_t;
typedef void(*Behavior_Table_pf)(void); //行为列表

/*有限状态机*/
typedef struct _fsm
{
    unsigned char (*State_Change)(State_t *L_Sta,State_t *C_Sta); //判断状态是否改变
    State_t *Last_State;					//上一状态
    State_t *Current_State;					//当前状态
    State_t (*State_Table)[State_Column];	//状态驱动表,根据实际情况定义,此时表示状态驱动表是n行State_Column列
}FSM_t;
  1. 当建立一个状态机时,如底盘状态机时,进行如下初始化操作
FSM_t Chassis_Fsm;  	//底盘状态机
/*状态驱动表
	这个根据实际情况定义,此时状态个数为State_Line*State_Column */
State_t 		   Chassis_State_Table[State_Line][State_Column];
/*行为驱动表
	这个根据实际情况定义,此时行为个数为Behavior_Num*/
Behavior_Table_pf  Chassis_Behavior_Table[Behavior_Num];

/*初始化过程*/
 /*
 *定义并实现State_Line*State_Column个状态,并用他们初始
 *化状态驱动表Chassis_State_Table。
 *定义并实现Behavior_Num个行为,并用他们初始化行为驱动表
 *Chassis_Behavior_Table。
 */
 //将底盘状态机的状态驱动表指针指向Chassis_State_Table
 Chassis_FSM.State_Table = Chassis_State_Table;
  1. 初始化完成后,将其送入状态机处理函数
/*此时参数s1和s2就是查表序号或外部事件,要根据实际情况定义函数参数*/
void FSM_Deal(FSM_t *fsm, unsigned char s1, unsigned char s2)
{
    /*误操作判断*/
    if(s1 <= 0 || s1 > State_Line || s2 <= 0 || s2 > State_Column)
    {
			return;
    }

    /*状态指向*/
    fsm->Current_State = &fsm->State_Table[s1-1][s2-1];

    /*状态变化*/
    if( fsm->State_Change(fsm->Last_State,fsm->Current_State) == 1)
    {
        fsm->Current_State->State_Prepare();
    }

    /*保留上次状态*/
    fsm->Last_State = fsm->Current_State;

    /*执行状态*/
    fsm->Current_State->State_Process();
		
	/*执行实际行为*/
	fsm->Current_State->Behavior_Process();
}
  1. 状态机处理函数中,有两个重要步骤,执行状态和执行实际行为,下面举一个例子展示这两个步骤
/*假设系统跳入了底盘独立运动状态*/
static void Chassis_Indepen_State(void)				
{
	/* 对实际场景进行分析,得出行为驱动表索引值 */
	unsigned char bev_index = Scene_Analyse(&Chassis_FSM.Current_State);
	Chassis_FSM.Current_State->Behavior_Process = Chassis_Behavior_Table[bev_index];
}

/*此时已根据实际场景,找到了该模式下的具体行为*/
/*假设跳到了扭腰行为*/
static void Chassis_Wiggle_bhv(void)
{
	//一套流程后,最终执行此函数
}	

以上是我对状态机模式的一点理解,不足之处请在评论区指出。

参考资料:《C嵌入式编程设计模式》
https://bbs.huaweicloud.com/blogs/113179
https://www.zhihu.com/column/c_1136684995157577728

你可能感兴趣的:(机器人项目相关,其他)