也谈状态模式
看到上面的这个状态转换图,一般来说我们还想不到用状态机模式去解决,因为太简单了,简单得可能几行代码就处理了。
switch-case 大法
通常我们一般会使用下面的代码处理状态机流转的需求,
enum Event {
EVENT_LOGIN, // 登录事件
EVENT_LOGOUT, // 登出事件
EVENT_EXPIRED, // 登录过期事件
EVENT_UNKNOWN,
};
enum State {
STATE_START, // 初始状态
STATE_LOGIN, // 登录状态
STATE_UNKNOWN,
};
struct Context {
State state_;
};
void SwitchCaseMethod(Event event, Context &context) {
switch (event) {
case EVENT_LOGIN:
// do something for login event
context.state_ = STATE_LOGIN;
break;
case EVENT_LOGOUT:
// do something for logout event
context.state_ = STATE_START;
break;
case EVENT_EXPIRED:
// do something for expired event
context.state_ = STATE_START;
break;
default:
// ignore
break;
}
}
但是如果状态机的复杂度增加到下面这样,
使用 switch-case 写出来的很可能就会像面条一样了,
表驱动
当然我们如果做出适当的抽象,可以写出类似于下面的代码,
enum Event {
EVENT_LOGIN, // 登录事件
EVENT_LOGOUT, // 登出事件
EVENT_EXPIRED, // 登录过期事件
EVENT_UNKNOWN,
};
enum State {
STATE_START, // 初始状态
STATE_LOGIN, // 登录状态
STATE_UNKNOWN,
};
struct Context {
State state_;
};
typedef void (*EventHandler)(Context &context);
void LoginHandler(Context &context) {}
void LogoutHandler(Context &context) {}
void ExpiredHandler(Context &context) {}
#define TABLE_SIZE 255
EventHandler EventHandlerTable[TABLE_SIZE] = {0};
void TableDriveInit() {
EventHandlerTable[EVENT_LOGIN] = LoginHandler;
EventHandlerTable[EVENT_LOGOUT] = LogoutHandler;
EventHandlerTable[EVENT_EXPIRED] = ExpiredHandler;
}
void TableDriveMethod(Event event, Context &context) {
if (event >= TABLE_SIZE) {
return;
}
auto handler = EventHandlerTable[event];
if (NULL != handler) {
handler(context);
}
}
代码的可维护性提高了好多,看起来很干净。但是如果事件增加到几十个,状态也增加到几十个,上面的方式也让人吃不消了,那么我们需要引出状态模式解决我们的问题了。
状态模式
状态模式的定义可以在这里找到,可以发现状态模式似乎和我们的处理逻辑有些差异,那就是抽象的状态方法只有一个 doAction,和我们 login、logout 和 expired 有些差异,那么我们只需要增加几个方法即可,就像下面这样,
enum Event {
EVENT_LOGIN, // 登录事件
EVENT_LOGOUT, // 登出事件
EVENT_EXPIRED, // 登录过期事件
EVENT_UNKNOWN,
};
enum State {
STATE_START, // 初始状态
STATE_LOGIN, // 登录状态
STATE_UNKNOWN,
};
struct Context {
State state_;
};
class AbstractState {
public:
virtual void onLogin(Context &context) {}
virtual void onLogout(Context &context) {}
virtual void onExpired(Context &context) {}
};
class StartState : public AbstractState {
public:
virtual void onLogin(Context &context) {
// do something and transfer to new state
context.state_ = STATE_LOGIN;
}
};
class LoginState : public AbstractState {
virtual void onLogout(Context &context) {
// do something and transfer to new state
context.state_ = STATE_START;
}
virtual void onExpired(Context &context) {
// do something and transfer to new state
context.state_ = STATE_START;
}
};
这样就能满足在每个状态下只做这个状态感兴趣的事情,其他的都可以忽略,这样我们能够应对大部分场景。
更进一步
上面我们所说的能够的应对大部分场景言外之意就是有些场景还是应对不了,比如事件经常变化的场景就需要改动抽象类,可参考 spring-statemachine 框架,框架把状态、事件和动作的注册进行了抽象,修改时不需要修改基类只需要修改注册的事件、状态和动作。
参考文献
游戏人工智能编程案例精粹,https://book.douban.com/subject/3081930/
代码大全第十八章,https://book.douban.com/subject/1477390//
知乎这个问题下有很多高质量的回答,https://www.zhihu.com/question/31845498
有赞工作流引擎,https://tech.youzan.com/workflow-engine-in-youzan-devops/
spring-statemachine 实践,https://cloud.tencent.com/developer/article/1534194
终极大法,https://book.douban.com/subject/21964984/
- 号外!号外!Python纳入高考内容了!人工智能时代就要来临了!
- 高颜值!域名5h.net和jb.cc纷纷易主
- 认识ASP.NET 5项目结构和项目文件xproj
- weblogic下部署应用时slf4j与logbak冲突的解决办法
- 介绍一位OWin服务器新成员TinyFox
- javascript: 带分组数据的Table表头排序
- 域名资讯:昨日域名成交2897个,成交额达300多万元
- 2017年12月编程语言排行榜:C语言再次崛起,有望成为2017年度编程语言
- WordPress 免插件仅代码实现 Gravatar 头像缓存
- Spring JDBCTemplate使用JNDI数据源
- 大家之前是不是误解了DC/OS与Kubernetes之间的关系
- CentOS 7 上部署Mono 4 和Jexus 5.6
- maven学习(下)利用Profile构建不同环境的部署包
- AS3初学者容易迷糊的几个问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- TensorFlow2.X学习笔记(4)--TensorFlow低阶API之AutoGraph相关研究
- TensorFlow2.X学习笔记(3)--TensorFlow低阶API之张量
- TensorFlow2.X学习笔记(2)--TensorFlow的层次结构介绍
- 深入浅出 Vue 中的 key 值
- TensorFlow2.X学习笔记(1)--TensorFlow核心概念
- 【项目实战】ODS 层创建&数据接入
- webpack3 升级到 webpack4 小记
- BigData-Apache HBase数据库
- Tungsten Fabric知识库丨这里有18个TF补丁程序,建议收藏
- BigData-消息队列框架Apache Kafka入门、原理解析
- BigData--Apache Flume框架
- 【项目实战】DWS 层创建&数据接入
- BigData--Hive数据仓库工具
- 读书笔记——《深入浅出 Webpack》( 送 XMind导图和电子书)
- BigData--MapReduce进阶(二)之工作机制