1 背景

刚好接触到业务分层,现在状态迁移比较耦合,不利于后期扩展,故想应用状态机在业务中。php中有symfony的workflow,java就更多了。本文章只针对过一遍状态机的思想以及实现最基础的功能。

1.1 早期状态

2,3年前的设计者可能没有思考过扩展和维护的的重要性,就应用了一大堆if else去做状态转换,对于短期来说会挺方便的,但是之后若有业务变更或者新增状态就会比较吃力,往往无从下手。

2 概念

2.1 组成
  • 现态:当前状态
  • 事件「条件」:触发条件或者事件
  • 动作:触发后的动作,非必须
  • 次态:指转移后的状态,触发事件后变成现态
2.2 特征
  • 每个对象只有有限个状态
  • 任何时刻只能存在一种状态
  • 每个状态可以通过某个事件能转移到另外的状态
2.3 表示方法
  • UML状态图
  • 状态表

3 实现

情景:一篇文章的状态,先准备好状态表

当前状态草稿待发布下架发布
完成编辑待发布   
发布文章 发布发布 
下架文章   下架

表 3.1 状态表

图 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 总结

在某些特定的情景,应用状态机具有提高代码可读性、可维护性、快速扩展状态等等优点。现代框架带有的状态机往往加入很多额外的功能,比如事件报警、状态机持久化和分布式状态机等。

参考文章: https://www.jianshu.com/p/20d7f7c37b03

您或许感兴趣

发表评论

电子邮件地址不会被公开。