有限状态机的 Java 实现

大部分的软件开发都是在处理业务逻辑,有些简单的逻辑通过 ifelse 判断可以解决,当遇到复杂多样的逻辑时,我们就要思考是否有其它的方式使得代码逻辑清晰、可扩展性强。

对,这就是设计模式介入的时候了。这次我们来简单探讨“有限状态机”这种新形式。

何为有限状态机

有限状态机在维基百科中的解释是:

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

咋一看好像很虚幻,我们先看一个请假流程的例子:

员工提交假单,这时假单是“待审批”状态,上级可以“同意”或“拒绝”,同意后变成“审批通过”状态,拒绝后变成“审批拒绝”状态。

这是一个非常简单的流程,如果用 ifelse 方式处理会发现逻辑判断和业务处理耦合在一起,造成代码逻辑不清晰,可扩展性不强等问题。

站在有限状态机的角度来看,可以抽象如下几个关键点:

  • 状态(State)

    即流程所处的节点状态,同上述例子的:“待审批”,“审批通过”,“审批拒绝”。

  • 事件(Event)

    即流程都是在节点上触发了某个事件才往下走更改状态的,如:“待审批”触发了“同意”事件才变成“审批通过”的。

  • 动作(Transition)

    即流程流转过程中具体的业务逻辑,如:“待审批”触发“同意”事件变成“审批通过”,这中间可能需要发送邮件通知,系统销假等操作。

设计一个简单版状态机

如上述分析,一个完整的状态机应该包括:状态,事件,动作,以及状态机入口。

状态,维护状态编码,以及该状态下可支持的动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.ArrayList;
import java.util.List;

import lombok.Getter;

/**
* 状态节点
* @author 张振伟
*/
public class State {

/** 状态编码 */
@Getter
private String stateCode;

/**
* 当前状态下可允许执行的动作
*/
@Getter
private List<Transition> transitions = new ArrayList<>();

public State(String stateCode, Transition... transitions) {
this.stateCode = stateCode;
for (Transition transition : transitions) {
this.transitions.add(transition);
}
}

// 添加动作
public void addTransition(Transition transition) {
transitions.add(transition);
}

@Override
public String toString() {
return stateCode;
}
}

事件,维护事件编码,以及事件附属的业务参数信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.Map;

import lombok.Getter;
import lombok.Setter;

/**
* 触发的事件
* @author 张振伟
*/
public class Event {

/** 事件标识(编码) */
@Getter
private String eventCode;

/** 附属的业务参数 */
@Getter
@Setter
private Map<Object, Object> attributes = null;

public Event(String eventCode) {
this.eventCode = eventCode;
}

public Event(String eventCode, Map<Object, Object> attributes) {
this.eventCode = eventCode;
this.attributes = attributes;
}

@Override
public String toString() {
return eventCode;
}
}

动作,维护触发该动作的事件,触发前状态,触发后状态,以及具体的动作内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import lombok.Getter;

/**
* 动作基类
* @author 张振伟
*/
public abstract class Transition {

/** 触发事件 */
@Getter
private String eventCode;

/** 触发当前状态 */
@Getter
private State currState;

/** 触发后状态 */
@Getter
private State nextState;

public Transition(String eventCode, State currState, State nextState) {
super();
this.eventCode = eventCode;
this.currState = currState;
this.nextState = nextState;
}

/**
* 执行动作
* @author 张振伟
* @param event
* @return
*/
public State execute(Event event) {
System.out.println(String.format("当前是:%s 状态,执行:%s 操作后,流转成:%s 状态。", currState, eventCode, nextState));
if (this.doExecute(event)) {
return this.nextState;
} else {
return null;
}
}

/**
* 执行动作的具体业务
* @author 张振伟
* @param event
* @return
*/
protected abstract boolean doExecute(Event event);

}

状态机,维护该状态机所有支持的状态,以及调用入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.List;

import lombok.extern.slf4j.Slf4j;

/**
* 状态机基类
* @author 张振伟
*/
@Slf4j
public abstract class StateMachine {

/** 定义的所有状态 */
private static List<State> allStates = null;

/**
* 状态机执行事件
* @param stateCode
* @param event
* @return
*/
public State execute(String stateCode, Event event) {
State startState = this.getState(stateCode);
for (Transition transition : startState.getTransitions()) {
if (event.getEventCode().equals(transition.getEventCode())) {
return transition.execute(event);
}
}
log.error("StateMachine[{}] Can not find transition for stateId[{}] eventCode[{}]", this.getClass().getSimpleName(), stateCode, event.getEventCode());
return null;
}

public State getState(String stateCode) {
if (allStates == null) {
log.info("StateMachine declareAllStates");
allStates = this.declareAllStates();
}

for (State state : allStates) {
if (state.getStateCode().equals(stateCode)) {
return state;
}
}
return null;
}

/**
* 由具体的状态机定义所有状态
* @author 张振伟
* @return
*/
public abstract List<State> declareAllStates();
}

请假流程实例

根据上述定义好一个简单状态机,结合请假流程业务场景,可以如下实现该业务:

1,定义一个请假审批的状态机,包括三个状态,两个事件,两个动作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.ArrayList;
import java.util.List;

import net.oschina.statemachine.Event;
import net.oschina.statemachine.State;
import net.oschina.statemachine.StateMachine;
import net.oschina.statemachine.Transition;

/**
* 简单的请假审批状态机
*/
public class AuditStateMachine extends StateMachine {

@Override
public List<State> declareAllStates() { // 定义状态机的状态
List<State> stateList = new ArrayList<>();

State pendingState = new State(StateCodeContents.PENDING);
State passedState = new State(StateCodeContents.PASSED);
State refusedState = new State(StateCodeContents.REFUSED);

pendingState.addTransition(new PassTransition(pendingState, passedState));
pendingState.addTransition(new RefuseTransition(pendingState, refusedState));

stateList.add(pendingState);
stateList.add(passedState);
stateList.add(refusedState);

return stateList;
}

/** 定义“通过”动作 */
public class PassTransition extends Transition {
public PassTransition(State currState, State nextState) {
super(EventCodeContents.PASS, currState, nextState);
}
@Override
protected boolean doExecute(Event event) {
System.out.println("执行通过操作...");
return true;
}

}

/** 定义“拒绝”动作 */
public class RefuseTransition extends Transition {
public RefuseTransition(State currState, State nextState) {
super(EventCodeContents.REFUSE, currState, nextState);
}
@Override
protected boolean doExecute(Event event) {
System.out.println("执行拒绝操作...");
return true;
}

}

/** 事件编码 */
public class EventCodeContents {
public static final String PASS = "审核通过";
public static final String REFUSE = "审核拒绝";
}

/** 状态编码 */
public class StateCodeContents {
public static final String PENDING = "待审批";
public static final String PASSED = "审批通过";
public static final String REFUSED = "审批拒绝";
}
}

2,单元测试,“待审批”->“审批通过”场景:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestStateMachine {

@Test
public void test() {
StateMachine sm = new AuditStateMachine();
State state = sm.execute(StateCodeContents.PENDING, new Event(EventCodeContents.PASS));
}
}

// 执行结果:
当前是:待审批状态,执行:审核通过操作后,流转成:审批通过状态。
执行通过操作...

这样一个简单的有限状态机 Java 实现就完成了。

欢迎有问题小伙伴在下方留言!