文章首发于知乎专栏:
https://zhuanlan.zhihu.com/c_1136684995157577728
个人公众号TarysThink
状态(State)模式,就是状态机嘛。状态机很常见,我们总能遇到带条件的状态跳转场景,比如红绿灯,绿灯亮的状态之后一定要进行关绿灯打开黄灯,进入下一个状态黄灯亮,再比如收音机,关闭状态的下一个动作一定是开机,收音机进入开机状态。那么状态机怎么实现呢?
有这么一个需求,宋江命令李逵杀敌,李逵此时有很多种状态,李逵要根据自己不同的状态做出不同的反应:如果正在吃饭,就扔掉碗进入空闲态,如果处于空闲,就拿起斧子进入战斗状态,如果处于战斗状态,就不做响应。
这个需求让程序员林冲去实现,林冲代码如下;
void LikuiFight()
{
switch(LikuiStatus)
{
case EATING:
ThrowBowl();
break;
case IDLE:
TakeAxe();
break;
default:
DoNothing();
}
}
void ThrowBowl()
{
LikuiStatus = IDLE;
}
void TakeAxe()
{
LikuiStatus = FIGHTING;
}
此时又来一个需求,宋江还要命令李逵吃饭,如果正在吃饭,就不做响应,如果处于空闲,就拿起碗进入吃饭状态,如果处于战斗状态,就扔掉斧子进入空闲状态。宋江还要命令李逵休息。于是可以形成如下一个状态转移表:
林冲一看,这么简单,再加一个处理函数嘛,新增函数如下:
void LikuiEat()
{
switch(LikuiStatus)
{
case FIGHTING:
ThrowAxe();
break;
case IDLE:
TakeBowl();
break;
default:
DoNothing();
}
}
void ThrowAxe()
{
LikuiStatus = IDLE;
}
void TakeBowl()
{
LikuiStatus = EATING;
}
程序员吴用一看不行这不是大段的结构重复嘛,消除下重复,代码如下:
void LikuiAction()
{
switch(LikuiStatus)
{
case EATING:
switch(SongjiangCmd)
{
case EAT:
DoNothing();
break;
case FIGHT:
case IDLE:
ThrowBowl();
break;
default:
DoNothing();
}
break;
case FIGHING:
switch(SongjiangCmd)
{
case FIGHT:
DoNothing();
break;
case EAT:
case IDLE:
ThrowAxe();
break;
default:
DoNothing();
}
break;
case IDLE:
switch(SongjiangCmd)
{
case FIGHT:
TakeAxe();
break;
case EAT:
TakeBowl();
break;
case IDLE:
DoNothing();
break;
default:
DoNothing();
}
break;
default:
DoNothing();
}
}
林冲一看,不乐意了,你这代码为了复用搞得这么复杂,一个函数内条件判断这么多,还不如我的直观容易理解呢。
程序员大佬卢俊义对代码的理解已经达到一定高度,大佬看到林冲和吴用的代码,道:“你们看,题目里的表格是不是很清晰?清晰就对了,状态转移之所以复杂,原因在于它会产生状态与命令两个维度的组合,对吧?既然表格很清晰,那我们就把代码也写成表格的形式吧。”老卢的代码如下:
typedef void (*LikuiDoAction)();
LikuiDoAction likuiDoAction[3][3] =
{ /* EATING */ /* FIGHTING */ /* IDLE */
/* EAT */ {DoNothing, ThrowAxe, TakeBowl},
/* FIGHT */ {ThrowBowl, DoNothing, TakeAxe},
/* IDLE */ {ThrowBowl, ThrowAxe, DoNothing}
}
void LikuiAction()
{
likuiDoAction[SongjiangCmd][LikuiStatus]();
}
林冲吴用一看,我的天,一个判断都没有。
老卢接着说:“状态机场景写成这个样子已经可以了。设计模式里的状态模式,如果用C语言实现,会非常复杂,而且相比于我这份实现,收益远不足以抵消其复杂性,你们看看也就可以了,就当开阔一下眼界,实际工作中没人会这么写代码。
“如果要定义一个状态的结构体,应该包括哪些内容呢?状态有哪些元素?状态,根据一些命令,会转入下一个状态,所以状态的定义可以这样写”。
Typedef struct State
{
struct State *(*fight)(struct State *state);
struct State *(*eat)(struct State *state);
struct State *(*idle)(struct State *state);
}State;
状态有三种:
State fighting = {DoNothing, ThrowAxe, ThrowAxe};
State eating = {ThrowBowl, DoNothing, ThrowBowl};
State idle = {TakeAxe, TakeBowl, DoNothing};
代码如下:
State *curState;
State *DoNothing(State * state)
{
return curState;
}
State *ThrowAxe(State * state)
{
return &idle;
}
State *ThrowBowl(State * state)
{
return &idle;
}
State *TakeAxe(State * state)
{
return &fighting;
}
State *TakeBowl(State * state)
{
return &eating;
}
void Init()
{
curState = &idle;
}
void fight()
{
curState = curState->fight(curState);
}
void eat()
{
curState = curState->eat(curState);
}
void idle()
{
curState = curState->idle(curState);
}
从此之后,宋江命令李逵就可以随心所欲了。不过,极不推荐这种写法。