背景

背景需求很简单:我们已有一个执行单步状态迁移操作的服务。现在需要将单步状态迁移操作组合起来,成为自动地、尽可能多地向后迁移的状态机。

处理规则

有两种基本思路。

递归处理

在单步操作执行完毕、尚未返回结果之前,以该结果为基础,尝试再执行一次单步操作。这种方式的问题在于,如果状态机走得很远,嵌套堆栈可能会很深。

迭代处理

任何递归都可以改写为迭代。在这里则是在一次单步操作执行完毕、并获得返回结果之后,再以该结果为基础、尝试执行一次单步操作。这种方式会更优一些。

do-while方式

代码如下:

    @Override
    public StateModel transform(StateModel initState) {
        log.info("initState:{}", initState);

        /*
         * DONE linjun 2018-06-19 总共需要四个状态:
         * 最初状态,即入参
         * 每一次迁移的“上一”状态、“下一”状态。需要步进
         */
        StateModel prevState, nextState = initState;
        /*
         * 出参,应当比迁移结果“晚一步”。
         * 因为迁移操作是个“尝试”。尝试就可能成功、可能失败。
         * 但返回结果应当是最后一次成功的迁移结果
         */
        StateModel result = initState;
        /*
         * 本次状态迁移的"足迹"。
         * 状态机是个有向图,“足迹”中记录了每次迁移得到的状态,用来判断是否产生了有向环
         * 如果某次迁移后,得到了“足迹”中记录的某个状态,说明本次状态迁移产生了有向环
         * 产生有向环时,终止此次状态自动迁移(不抛出异常,因为修改某些数据后可能可以转向旁路)
         */
        List<State> footPrints = new LinkedList<>();
        do {
            // 将状态记录在足迹中。
            footPrints.add(nextState.getState());

            /*
             * 上一状态步进一步,追上下一状态
             * 如果是初始状态(第一步),则设置为initState
             * 否则,则根据上一次迁移结果,构造新的prveState
             */
            prevState = nextState ;
            log.debug("prevState:{}", prevState);

            // 下一状态步进一步,与上一状态甩开一步之差
            // 只有在prevState有效的情况下才继续迁移
            if (prevState == null) {
                break;
            }
            // 记录上一次的结果:最终结果实际是prev
            result = prevState;
            nextState = stateTransformer.transform(prevState);
            log.debug("nextModel:{}", nextState);

        } while (isNextModelAvailable(prevState, nextState, footPrints));

        log.info("result:{}", result);
        return result;

    }

while方式

    @Override
    public StateModel transform(StateModel initState) {
        log.info("initState:{}", initState);
        // [start] linjun 2018-6-19
        /************************************
         * 初始化操作
         ***********************************/
        // 两个中间变量。其中nextState已经走了一步了
        StateModel prevState = new StateModel(), nextState = initState;
        // stateTransformer.transform(prevState);
        // 返回值,默认为初始状态
        StateModel result = initState;
        // 足迹
        List<State> footPrints = new LinkedList<>();
        log.debug("before while: prevModel:{},nextState:{},footPrints:{}",
                prevState, nextState, footPrints);
        // [end] linjun 2018-6-19

        // [start] linjun 2018-6-19
        /************************************
         * 状态连续迁移
         ***********************************/
        while (isNextModelAvailable(prevState, nextState, footPrints)) {
            // 迁移前状态步进。如果是初始状态,那么不用重新构建
            prevState = nextState;
            log.debug("prevState:{}", prevState);
            /*
             * 当prevState有效时:
             * 记录迁移前的状态足迹,准备与迁移后做比较
             * 做一次迁移操作,此时next比prev超前一步
             * 将prev记录为返回结果:因为进入了while,就说明此次迁移是正确的
             */
            footPrints.add(prevState.getState());
            nextState = stateTransformer.transform(prevState);
            result = prevState;
            log.debug("prevState:{},nextState:{}", prevState, nextState);

        }
        // [end] linjun 2018-6-19

        log.info("result:{}", result);
        return result;
    }

for循环方式

代码如下

    @Override
    public StateModel transform(StateModel initState) {
        log.info("initState:{}", initState);
        /*
         * 出参,应当比迁移结果“晚一步”。
         * 因为迁移操作是个“尝试”。尝试就可能成功、可能失败。
         * 但返回结果应当是最后一次成功的迁移结果
         */
        StateModel result = initState;
        /*
         * 本次状态迁移的"足迹"。
         * 状态机是个有向图,“足迹”中记录了每次迁移得到的状态,用来判断是否产生了有向环
         * 如果某次迁移后,得到了“足迹”中记录的某个状态,说明本次状态迁移产生了有向环
         * 产生有向环时,终止此次状态自动迁移(不抛出异常,因为修改某些数据后可能可以转向旁路)
         */
        Collection<State> footPrints = new HashSet<>();

        try {
            /*
             * 两个指针:prevState指向状态迁移前的状态;postState指向状态迁移后的状态。
             * prevState和postState的初始化操作,是为了统一isNextModelAvailable方法和循环体中的处理
             */
            for (StateModel prevState = new StateModel(), postState = initState; isNextModelAvailable(
                    prevState, postState,
                    footPrints); footPrints.add(prevState.getState())) {
                // prevState步进,追上next
                prevState = postState;
                // 记录返回结果
                result = postState;
                // nextState步进,超前prev一步
                postState = stateTransformer.transform(prevState);
                log.debug("prevState:{},nextState:{}", prevState, postState);
            }
        } catch (BaseException ex) {
            // TODO linjun 2018-06-19 处理中间过程抛出的异常
        }

        log.info("result:{}", result);
        return result;

    }

对比

do-while方式有点类似乐观锁:先尝试做一次操作;如果这次操作结果满足while条件,然后再尝试下一次操作。而while则类似悲观锁,必须先做while判断,只有在所有前置条件都满足的情况下,才能执行这次操作。
for呢?本质上for也是一个while循环。for的优点(原生for循环,不是foreach或stream那种)在于它把初始化操作、业务操作、步进操作给严格的区分开了。这样在代码样式上会更清晰一些;而不像其它两个循环体内乱七八糟。不过由于大部分for循环都很简单,因此for循环括号内都只支持一个语句。如果初始化、步进操作太多,就不得不写到循环体内了。