状态模式
状态模式顾名思义就是实现状态机时使用的一种模式。
在嵌入式软件开发中,我们经常会遇到各种状态机。举个最简单的例子:一个设备有两种状态,如下图:
通常,我们实现该状态机的伪码如下:
enum StateEnum {
OpenState;
CloseState;
};
enum OperatinEnum {
LongPressButton;
ShortPressButton;
}
int StateProcess(int currentState, int operation)
{
if (operation == LongPressButton) {
if (currentState == OpenState) {
// ...
return CloseState;
}
} else if (operation == ShortPressButton)
if (currentState == CloseState) {
// ...
return OpenState;
}
}
// 状态切换失败,保持当前状态
return currentState;
}
如果我们要增加sleep状态,如下图:
那么,我们需要把代码修改为如下(带"//–add"的行是本次修改的代码):
enum StateEnum {
OpenState;
CloseState;
SleepState; //--add
};
enum OperatinEnum {
LongPressButton;
ShortPressButton;
FreeOver5Mins; //--add
}
int StateProcess(int currentState, int operation)
{
if (operation == LongPressButton) {
if (currentState == OpenState) {
// ...
return CloseState;
}
} else if (operation == ShortPressButton)
if (currentState == CloseState) {
// ...
return OpenState;
} else if (currentState == SleepState) { //--add
// ... //--add
return OpenState; //--add
}
} else if (operation == FreeOver5Mins) { //--add
if (currentState == OpenState) { //--add
// ... //--add
return SleepState; //--add
}
}
// 状态切换失败,保持当前状态
return currentState;
}
这还只是一个非常非常简单的状态机。但说实话,写到这个程度我已经很难检查代码的实现是否正确了。所以,这种实现方式的缺点显而易见:1) if-else太多太复杂,即使比较简单的状态机也很难让人脑一下子看明白;2) 明显违反了开闭原则。
那么,按照GOF的状态模式的思想,我们应该怎么来实现呢?我们应该按“状态”将上面的代码拆分开,把每种状态作为一个单独的类。当增加sleep状态时,我们就增加sleep状态类。伪码如下:
enum StateEnum {
OpenState;
CloseState;
SleepState; //--add
};
enum OperatinEnum {
LongPressButton;
ShortPressButton;
FreeOver5Mins; //--add
}
// 定义State基类
struct State {
struct State* (*LongPressButtonOperation)(); // LongPressButton的操作,返回next state
struct State* (*ShortPressButtonOperation)();
struct State* (*FreeOver5MinsOperation)(); //--add
}
// 定义OpenState类,继承State基类
struct OpenState {
... 根据本状态迁移的实际情况,重写基类中的方法
}
// 定义CloseState类,继承State基类
struct CloseState {
... 根据本状态迁移的实际情况,重写基类中的方法
}
// 定义SleepState类,继承State基类 //--add
struct SleepState { //--add
... 根据本状态迁移的实际情况,重写基类中的方法 //--add
}
// 定义StateProcessor(),驱动状态迁移
StateProcessor() {
...
}
这里我只简单的写了下状态模式的伪码,不打算详细展开了。主要原因是,我个人觉得,这种实现方式虽然明显比最前面的方式有所改进。但我还是觉得这样实现状态机并不是很好。因为这种方式主要解决了增加状态的问题,并没有解决增加操作的问题,然后再实际中,一旦状态机发生变化,大多数场景都是状态和操作都有变化。就前面的例子来说,增加sleep状态,同时就增加了FreeOver5Mins操作,且open状态也会增加FreeOver5Mins操作。
反正,我是没怎么见过状态机增加状态时,不增加操作的情况。所以,我自己通常会按另一个方式来实现状态机,我会把每种 current state – operation – next state 作为一个类,然后用StateProcessor驱动状态迁移。
enum StateEnum {
OpenState;
CloseState;
SleepState; //--add
};
enum OperatinEnum {
LongPressButton;
ShortPressButton;
FreeOver5Mins; //--add
}
// 定义State--Operation基类
struct State {
int currentState;
int operation;
int (*Operation)();
int nextState;
}
struct State g_fsm[] = {
{ OpenState,
LongPressButton,
xxxOperation(), // 根据本状态迁移的实际情况,重写基类中的方法
CloseState
},
{ CloseState,
ShortPressButton,
xxxOperation(), // 根据本状态迁移的实际情况,重写基类中的方法
OpenState,
},
{ OpenState, //--add
FreeOver5Mins, //--add
xxxOperation(), //--add
SleepState, //--add
},
{ SleepState, //--add
ShortPressButton, //--add
xxxOperation(), //--add
OpenState, //--add
},
};
// 定义StateProcessor(),驱动状态迁移
int StateProcessor(int currentState, int operation) {
for (i = 0; i < g_fsm[]的成员数; i++) {
if (currentState == g_fsm[i].currentState) && (operation == g_fsm[i].operation) {
g_fsm[i].xxxOperation();
return g_fsm[i].nextState;
}
}
// 状态切换失败,保持当前状态
return currentState;
}
我认为这种方式跟GOF的状态模式是有区别的,但我个人认为这种方式更完美。