Java 实现有限状态机的推荐方案

文章来源:https://blog.csdn.net/w605283073/article/details/121345982

一、背景

平时工作开发过程中,难免会用到状态机(状态的流转)。

如奖学金审批流程、请假审批流程、竞标流程等,都需要根据不同行为转到不同的状态。

下面是一个简单的模拟状态机:

图片说明

有些同学会选择将状态定义为常量,使用 if else 来流转状态,不太优雅。

有些同学会考虑将状态定义为枚举。

但是定义为枚举之后,大多数同学会选择使用来流转状态:
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/

import lombok.Getter;

public enum State {

    STATE_A("A"),
    STATE_B("B"),
    STATE_C("C"),
    STATE_D("D");

    @Getter
    private final String value;

    State(String value) {
        this.value = value;
    }

    public static State getByValue(String value) {
        for (State state : State.values()) {
            if (state.getValue().equals(value)) {
                return state;
            }
        }
        return null;
    }

    /**
     * 批准后的状态
     */
    public static State getApprovedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                return STATE_B;
            case STATE_B:
                return STATE_C;
            case STATE_C:
                return STATE_D;
            case STATE_D:
            default:
               throw new IllegalStateException("当前已终态");
        }

    }

    /**
     * 拒绝后的状态
     */
    public static State getRejectedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                throw new IllegalStateException("当前状态不支持拒绝");
            case STATE_B:
            case STATE_C:
            case STATE_D:
            default:
                return STATE_A;
        }
    }
}

上面这种写法有几个弊端:

(1)每次获取枚举值都要循环一次当前枚举的所有常量,时间复杂度是O(N),虽然耗时非常小,但总有些别扭,作为有追求的程序员,应该尽量想办法优化掉。

(2) 总感觉使用 switch-case 实现状态流转,更多的是面向过程的产物。虽然可以实现功能,但没那么“面向对象”,既然 State 枚举就是用来表示状态,如果同意和拒绝可以通过 State 对象的方法获取就会更直观一些。

二、推荐方式

2.1 自定义的枚举

通常状态流转有两种方向,一种是赞同,一种是拒绝,分别流向不同的状态。

由于本文讨论的是有限状态,我们可以将状态定义为枚举比较契合,除非初态和终态,否则赞同和拒绝都会返回一个状态。

下面只是一个DEMO, 实际编码时可以自由发挥。

该 Demo 的好处是:

1 使用 CACHE缓存,避免每次通过 value 获取 State都循环 State 枚举数组

2 定义【同意】和【拒绝】抽象方法,每个 State 通过实现该方法来流转状态。

3 状态的定义和转换都收拢在一个枚举中,更容易维护

虽然代码看似更多一些,但是更“面向对象”一些。

package basic;

import lombok.Getter;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public enum State {
    /**
     * 定义状态,并实现同意和拒绝的流转
     */
    STATE_A("A") {
        @Override
        State getApprovedState() {
            return STATE_B;
        }

        @Override
        State getRejectedState() {
            throw new IllegalStateException("STATE_A 不支持拒绝");
        }
    },
    STATE_B("B") {
        @Override
        State getApprovedState() {
            return STATE_C;
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    },
    STATE_C("C") {
        @Override
        State getApprovedState() {
            return STATE_D;
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    },
    STATE_D("D") {
        @Override
        State getApprovedState() {
             throw new IllegalStateException("当前已终态");
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    };
// 更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/

    @Getter
    private final String value;

    State(String value) {
        this.value = value;
    }

    private static final Map<String, State> CACHE;

    static {
        CACHE = Arrays.stream(State.values()).collect(Collectors.toMap(State::getValue, Function.identity()));
    }

    public static State getByValue(String value) {
        return CACHE.get(value);
    }

    /**
     * 批准后的状态
     */
    abstract State getApprovedState();

    /**
     * 拒绝后的状态
     */
    abstract State getRejectedState();
}

测试代码

package basic;


import static basic.State.STATE_B;


public class StateDemo {

    public static void main(String[] args) {

        State state = State.STATE_A;


        // 一直赞同

        State ApprovedState;

        do {

            ApprovedState = state.getApprovedState();

            System.out.println(state + "-> Approved:" + ApprovedState);

            state = ApprovedState;

        } while (state != State.STATE_D);



        // 获取某个状态的赞同和拒绝后的状态

        System.out.println("STATE_B Approved ->" + STATE_B.getApprovedState());

        System.out.println("STATE_C reject ->" + State.getByValue("C").getRejectedState());

        System.out.println("STATE_D reject ->" + State.getByValue("D").getRejectedState());

    }

}
package basic;

import static basic.State.STATE_B;

public class StateDemo {
    public static void main(String[] args) {
        State state = State.STATE_A;

        // 一直赞同
        State approvedState;
        do {
            approvedState = state.getApprovedState();
            System.out.println(state + "-> approved:" + approvedState);
            state = approvedState;
        } while (state != State.STATE_D);


        // 获取某个状态的赞同和拒绝后的状态
        System.out.println("STATE_B approved ->" + STATE_B.getApprovedState());
        System.out.println("STATE_C reject ->" + State.getByValue("C").getRejectedState());
        System.out.println("STATE_D reject ->" + State.getByValue("D").getRejectedState());
    }
}

2.2 外部枚举

假如该枚举是外部提供,只提供枚举常量的定义,不提供状态流转,怎么办?

三、总结

本文结合自己的理解,给出一种推荐的有限状态机的写法。

给出了自有状态枚举和外部状态枚举的解决方案,希望对大家有帮助。
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/

#Java开发##Java##学习路径#
全部评论
转载都不注明出处吗? https://blog.csdn.net/w605283073/article/details/121345982  转自明明如月学长的文章请注明出处否则找牛客官方举报
1 回复 分享
发布于 2022-07-19 23:55
写的不错 很有用
点赞 回复 分享
发布于 2022-02-15 19:52

相关推荐

评论
点赞
8
分享
牛客网
牛客企业服务