C常用设计模式——状态模式

文章首发于知乎专栏:

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;
}

此时又来一个需求,宋江还要命令李逵吃饭,如果正在吃饭,就不做响应,如果处于空闲,就拿起碗进入吃饭状态,如果处于战斗状态,就扔掉斧子进入空闲状态。宋江还要命令李逵休息。于是可以形成如下一个状态转移表:

C常用设计模式——状态模式_第1张图片

林冲一看,这么简单,再加一个处理函数嘛,新增函数如下:

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);
}

从此之后,宋江命令李逵就可以随心所欲了。不过,极不推荐这种写法。

你可能感兴趣的:(架构设计)