背景
背景需求很简单:我们已有一个执行单步状态迁移操作的服务。现在需要将单步状态迁移操作组合起来,成为自动地、尽可能多地向后迁移的状态机。
处理规则
有两种基本思路。
递归处理
在单步操作执行完毕、尚未返回结果之前,以该结果为基础,尝试再执行一次单步操作。这种方式的问题在于,如果状态机走得很远,嵌套堆栈可能会很深。
迭代处理
任何递归都可以改写为迭代。在这里则是在一次单步操作执行完毕、并获得返回结果之后,再以该结果为基础、尝试执行一次单步操作。这种方式会更优一些。
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循环括号内都只支持一个语句。如果初始化、步进操作太多,就不得不写到循环体内了。