理解状态机原理及实践

基本概念

  • 有限状态机FSM

  • 描述事物的有限状态机模型的元素由以下组成:

    1. 状态(State):事物的状态,包括初始状态和所有事件触发后的状态
    2. 事件(Event):触发状态变化或者保持原状态的事件
    3. 行为或转换(Action/Transition):执行状态转换的过程
    4. 检测器(Guard):检测某种状态要转换成另一种状态的条件是否满足
  • https://www.jianshu.com/p/37281543f506

  • 深入浅出理解有限状态机

  • 状态机的要素

  • 状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
    1. 现态:是指当前所处的状态。
    2. 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
    3. 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
    4. 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

代码实现

  1. 定义状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public enum ProcessState {

    INIT(0, "未开始"),
    READY_ONE_SIDE(1, "一方准备完成"),
    READY_ALL(2, "双方准备完成"),
    CHAT(3, "发言完成"),
    END(4, "结束"),
    ;

    private int val;
    private String desc;

    ProcessState(int val, String desc) {
    this.val = val;
    this.desc = desc;
    }

    public int getVal() {
    return val;
    }
    }
  2. 定义事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public enum ProcessEvent {

    READY_ONE_SIDE(1, "一方准备"),
    READY_ALL(2, "另一方准备"),
    CHAT(3, "发言"),
    FORBID_ALL(4, "全员禁言"),
    ;

    private Integer code;
    private String desc;

    ProcessEvent(Integer code, String desc) {
    this.code = code;
    this.desc = desc;
    }

    public Integer getCode() {
    return code;
    }
    }
  3. 定义状态机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ProcessEventConfig {

    private ProcessEvent event;
    private ProcessState fromState;
    private ProcessState toState;

    public String desc(){
    return fromState.getDesc() + "_" + event.getDesc() + "_" + toState.getDesc();
    }
    }


    List<ProcessEventConfig> configList = new ArrayList<>(16);
    configList.add(new ProcessEventConfig(ProcessEvent.READY_ONE_SIDE, ProcessState.INIT, ProcessState.READY_ONE_SIDE));
    configList.add(new ProcessEventConfig(ProcessEvent.READY_ALL, ProcessState.READY_ONE_SIDE, ProcessState.READY_ALL));
    configList.add(new ProcessEventConfig(ProcessEvent.CHAT, ProcessState.READY_ALL, ProcessState.CHAT));
    configList.add(new ProcessEventConfig(ProcessEvent.FORBID_ALL, ProcessState.CHAT, ProcessState.END));

    Map<ProcessEvent, ProcessEventConfig> eventResultStateConfigMap = new EnumMap<>(ProcessEvent.class);

    configList.forEach(eventConfig -> eventResultStateConfigMap.put(eventConfig.getEvent(), eventConfig));

  4. 触发状态变化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public boolean fire(ProcessEvent event, Process process) {

    ProcessEventConfig config = Optional.ofNullable(eventResultStateConfigMap.get(event)).orElseThrow(() -> ExceptionCodeEnum.AGRS_INVALID.newException("不存在该事件"));

    if (process.getStatus() != config.getFromState().getVal()) {
    return false;
    }
    process.setRemark(config.desc());
    process.setStatus(config.getToState().getVal());

    //DB改变状态
    /*update table set status=${status} WHERE id={id} AND status=${beforeStatus}*/
    boolean suc = processManager.updateStatus(process, config.getFromState().getVal());
    if (suc) {
    //变更成功,业务逻辑
    }
    return suc;
    }