干掉项目中杂乱的 if-else,试试状态模式,这才是优雅的实现方式!
IF-ELSE 方式
原来以为写一个简单的类型翻译器花不了太多时间,可是真做起来,才发现要注意的点太多了。
首先是处理容器的开启和闭合,这就需要使用栈
来保存预期的下一个字符类型,再对比栈顶字符类型和当前处理字符,决定解析的结果。
还要注意类型嵌套的情况下,内层嵌套的容器作为外层容器的元素被解析完成时,需要修改外层容器的预期字符。而且 Map 作为一种相对 Set 和 List 比较特殊的容器,还要处理它的左右元素。同时还不能忘记处理各种异常,如未知字符、容器内是原始类型、容器未正确闭合等。
而这些逻辑混杂在一块就更添复杂度了,通常是一遍代码写下来挺顺畅,找几个特殊的 case 一验证,往往就有没有考虑到的点,你以为解决了这个点就好了,殊不知这个问题点的解决方案又引起了另一个问题。
最终修修补补好多次,终于把代码写完了,连优化的想法都没了,担心又引入新的问题。更多 Java 核心技术教程:https://github.com/javastacks/javastack,一起来学习吧。
最终的伪代码如下:
public String parseToFullType() throws IllegalStateException {
StringBuilder sb = new StringBuilder();
for (; ; this.scanner.next()) {
Character currentChar = scanner.current();
if (currentChar == '\uFFFF') {
return sb.toString();
}
if (isCollection()) {
if (CollectionEnd()) {
dealCollectionEleEnd();
}else {
throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());
}
} else if (isWrapperType()) {
dealSingleEleEnd();
} else if (parseStart()) {
if (collectionStart()) {
putCollecitonExpectEle()
}
} else {
throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());
}
}
状态机方式
状态机
。状态机
有限状态机(finite-state machine,缩写:FSM)又称有限状态自动机(finite-state automation,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
状态 State:状态是一个系统在其生命周期的某一刻时的运行状态,如驾车的例子中状态就包括 正常速度行驶、停车和低速行驶三种状态。 事件 Event:事件就是某一时刻施加于系统的某个信号,在上面的例子中事件是指红灯、绿灯和黄灯。所有的状态变化都要依赖事件,但事件也可能导致状态不发生变化,如正常行驶中遇到绿灯就不用做什么反应。 变换 Transition:变换是在事件发生之后系统要做出的状态变化,如上面例子中的减速、停车或加速。 动作 Action:动作是同样是事件发生之后系统做出的反应,不同的是,动作不会改变系统状态,像驾车遇到红灯停车后,喝水这个动作没有对系统状态造成影响。
状态拆分
首先是确定状态,我定义了 Start/SetStart/SetEle/ListStart/ListEel/MapStart/MapLeft/MapRight 八种基础状态,由于一次只解析一个类型,容器闭合就代表着解析结束,所以没有对各个容器设置结束状态。又因为有状态嵌套的存在,而一个状态没法表达状态机的准确状态,需要使用栈来存储整体的解析状态,我使用这个栈为空来代表 End 状态,又省略了一个状态。
再拆分事件,事件是扫描到的每一个字符,由于字符种类较多,而像 integer 和 double、String 和 Long 的处理又没有什么区别,我将事件类型抽象为 包装类型元素(WRAPPED_ELE),原始类型元素(PRIMITIVE_ELE),MAP、List 和 Set 五种。
变幻和动作都是事件发生后系统的反应,在我的需要里需要转变解析状态,并将结构结果保存起来。这里我将它们整体抽象为一个事件处理器接口,如:
public interface StateHandler {
/**
* @param event 要处理的事件
* @param states 系统整体状态
* @param result 解析的结果
*/
void handle(Event event, Stackstates, StringBuilder result);
}
代码示例
public class MapLeftHandler implements StateHandler {
@Override
public void handle(Event event, Stackstates, StringBuilder result) {
// 这里是核心的 Action,将单步解析结果放到最终结果内
result.append(",");
result.append(event.getParsedVal());
// 状态机的典型处理方式,处理各种事件发生在当前状态时的逻辑
switch (event.getEventType()) {
case MAP:
states.push(State.MAP_START);
break;
case SET:
states.push(State.SET_START);
break;
case LIST:
states.push(State.LIST_START);
break;
case WRAPPED_ELE:
// 使用 pop 或 push 修改栈顶状态来修改解析器的整体状态
states.pop();
states.push(State.MAP_RIGHT);
break;
case PRIMITIVE_ELE:
// 当前状态不能接受的事件类型要抛异常中断
throw new IllegalStateException("unexpected primitive char '" + event.getCharacter() + "' at position " + event.getIndex());
default:
}
}
}
public static String parseToFullType(String shortenType) throws IllegalStateException {
StringBuilder result = new StringBuilder();
StringCharacterIterator scanner = new StringCharacterIterator(shortenType);
Stackstates = new Stack<>();
states.push(State.START);
for (; ; scanner.next()) {
char currentChar = scanner.current();
if (currentChar == '\uFFFF') {
return result.toString();
}
// 使用整体状态为空来代表解析结束
if (states.isEmpty()) {
throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());
}
// 将字符规整成事件对象,有利于参数的传递
Event event = Event.parseToEvent(currentChar, scanner.getIndex());
if (event == null) {
throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());
}
// 这里需要一个 Map 来映射状态和状态处理器
STATE_TO_HANDLER_MAPPING.get(states.peek()).handle(event, states, result);
}
}