有限状态机-状态表实现
1 背景
刚好接触到业务分层,现在状态迁移比较耦合,不利于后期扩展,故想应用状态机在业务中。php中有symfony的workflow,java就更多了。本文章只针对过一遍状态机的思想以及实现最基础的功能。
1.1 早期状态
2,3年前的设计者可能没有思考过扩展和维护的的重要性,就应用了一大堆if else去做状态转换,对于短期来说会挺方便的,但是之后若有业务变更或者新增状态就会比较吃力,往往无从下手。
2 概念
2.1 组成
- 现态:当前状态
- 事件「条件」:触发条件或者事件
- 动作:触发后的动作,非必须
- 次态:指转移后的状态,触发事件后变成现态
2.2 特征
- 每个对象只有有限个状态
- 任何时刻只能存在一种状态
- 每个状态可以通过某个事件能转移到另外的状态
2.3 表示方法
- UML状态图
- 状态表
- 等
3 实现
情景:一篇文章的状态,先准备好状态表
当前状态 | 草稿 | 待发布 | 下架 | 发布 |
完成编辑 | 待发布 | |||
发布文章 | 发布 | 发布 | ||
下架文章 | 下架 |
表 3.1 状态表

3.1 状态机
状态机我们只准备有两个动作,一个是加入状态表,另一个是触发事件。
/** * 状态类 */ class State { public $startState; public $nextState; public $event; public $doAction; } /** * Finite State Machine */ class FSM { public $state; protected $stateTable = []; /** * 加入状态表 * * @param int $startState * @param int $event * @param int $nextState * @param array $doAction * @return void */ public function addTable($startState, $event, $nextState, $doAction) { $state = new State; $state->startState = $startState; $state->event = $event; $state->nextState = $nextState; $state->doAction = $doAction; $this->stateTable[] = $state; } /** * 触发事件 * * @param int $event * @return void */ public function dispatch($event) { foreach($this->stateTable as $state){ if ($state->startState === $this->state && $state->event === $event) { if (!is_null($state->doAction)) { list($class, $method) = $state->doAction; if (class_exists($class) && ($instance = new $class) && method_exists($instance, $method)){ $instance->{$method}(); } } $this->setCurrentState($state->nextState); return; } } // 这里可以添加没有找到处理逻辑 } public function setCurrentState($state) { $this->state = $state; } public function getCurrentState() { return $this->state; } }
3.2 业务枚举类
这里的业务指的就是文章类
/** * 文章类 */ class Post { /** * 状态枚举 */ const DRAFT = 1; const PREPARE = 2; const CLOSE = 3; const PUBLISHED = 4; const STATUS_MAP = [ self::DRAFT => '草稿', self::PREPARE => '待发布', self::CLOSE => '下架', self::PUBLISHED => '已发布', ]; /** * 事件枚举 */ const EDIT_COMPLETED_EVENT = 1; const PUBLISH_EVENT = 2; const CLOSE_EVENT = 3; const EVENT_MAP = [ self::EDIT_COMPLETED_EVENT => '完成编辑', self::PUBLISH_EVENT => '发布文章', self::CLOSE_EVENT => '下架文章', ]; // 事件动作 public function editComplete() { echo '完成编辑'; } public function publish() { echo '文章发布'; } public function close() { echo '下架文章'; } }
3.3 调用
// 前置定义 $postFsm = new FSM; $postFsm->addTable(Post::DRAFT, Post::EDIT_COMPLETED_EVENT, Post::PREPARE, ['Post', 'editComplete']); $postFsm->addTable(Post::PREPARE, Post::PUBLISH_EVENT, Post::PUBLISHED, ['Post', 'publish']); $postFsm->addTable(Post::CLOSE, Post::PUBLISH_EVENT, Post::PUBLISHED, ['Post', 'publish']); $postFsm->addTable(Post::PUBLISHED, Post::CLOSE_EVENT, Post::CLOSE, ['Post', 'close']); // 业务逻辑 $postFsm->setCurrentState(Post::DRAFT); $postFsm->dispatch(Post::EDIT_COMPLETED_EVENT); $postFsm->dispatch(Post::PUBLISH_EVENT); $postFsm->dispatch(Post::CLOSE_EVENT); $postFsm->dispatch(Post::PUBLISH_EVENT); var_dump(Post::STATUS_MAP[$postFsm->getCurrentState()]); /** * 完成编辑 * 文章发布 * 下架文章 * 文章发布 * string(9) "已发布" */
3.4 扩展
- 如果状态值改变「直接改类即可」
- 如果状态值删除「删除前置定义即可」
- 如果状态值方向改变「修改前置定义即可」
- 如果新增状态「前置定义新增一条即可」
4 总结
在某些特定的情景,应用状态机具有提高代码可读性、可维护性、快速扩展状态等等优点。现代框架带有的状态机往往加入很多额外的功能,比如事件报警、状态机持久化和分布式状态机等。