使用【状态模式&有限状态机】处理业务流转

1. 状态模式

1.1. 何为状态模式

状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式。

状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。

总结来讲,就是什么状态就做什么事,允许一个对象在其内部状态改变时改变它的行为 也可以说是数据驱动

1.2. 结构

状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

1.3. 角色:

  • 环境(Context )角色,也称上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
  • 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
  • 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。

1.4. 抽象代码演示:

抽象状态类:

csharp 复制代码
public interface State {
    /**
     * 状态对应的处理
     */
    public void handle(Context context);

    /**
     * 获得当前的状态
     */
    public void getState();
}

环境角色类:

typescript 复制代码
public class Context {
    //持有一个State类型的对象实例
    private State state;

    /**
     * 初始化状态
     *
     * @param state
     */
    public Context(State state) {
        this.state = state;
    }

    /**
     * 获得当前的状态
     *
     * @param state
     * @return
     */
    public State getState(State state) {
        return state;
    }

    /**
     * 改变当前的状态
     *
     * @param state
     */
    public void setState(State state) {
        this.state = state;
    }

    /**
     * 执行状态类实现的方法
     */
    public void request() {
        //转调state来处理
        state.handle(this);
    }
}

具体状态类:

ConcreteStateA:

typescript 复制代码
public class ConcreteStateA implements State {
    @Override
    public void handle(Context context) {
        //A->B
        //Next:ConcreteStateB
        context.setState(new ConcreteStateB());
        System.out.println("当前状态是A,下一状态是B");
    }

    @Override
    public void getState() {
        System.out.println("当前状态是A");
    }
}

ConcreteStateB:

typescript 复制代码
public class ConcreteStateB implements State {
    @Override
    public void handle(Context context) {
        //B->C
        //Next:ConcreteStateC
        context.setState(new ConcreteStateC());
        System.out.println("当前状态是B,下一状态是C");
    }

    @Override
    public void getState() {
        System.out.println("当前状态是B");
    }
}

ConcreteStateC:

typescript 复制代码
public class ConcreteStateC implements State {
    @Override
    public void handle(Context context) {
        //C->A
        //Next:ConcreteStateA
        context.setState(new ConcreteStateA());
        System.out.println("当前状态是C,下一状态是A");
    }

    @Override
    public void getState() {
        System.out.println("当前状态是C");
    }
}

客户端类:

typescript 复制代码
public class Client {
    public static void main(String[] args) {
        //初始化A状态
        Context context = new Context(new ConcreteStateA());
        //由A驱动,Next=B
        context.request();
        //由B驱动,Next=C
        context.request();
        //由C驱动,Next=A
        context.request();
    }
}

执行结果:

上述实例展示的是:A->B->C->A 状态转移过程,这是由3个状态组成的环状转移图,非常好懂,用他展示状态模式非常形象。一些简单的好理解的状态图,非常适合状态模式去"解耦"。

2. 有限状态机(Finite State Machine, FSM)

2.1. 何为有限状态机:

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

像我们生活中在公路上驾驶汽车就像在维护一个状态机,遇到红灯就停车喝口水,红灯过后再继续行车,遇到了黄灯就要减速慢行。而实现状态机前要首先明确四个主体:

状态 State:状态是一个系统在其生命周期的某一刻时的运行状态,如驾车的例子中状态就包括 正常速度行驶、停车和低速行驶三种状态。

事件 Event:事件就是某一时刻施加于系统的某个信号,在上面的例子中事件是指红灯、绿灯和黄灯。所有的状态变化都要依赖事件,但事件也可能导致状态不发生变化,如正常行驶中遇到绿灯就不用做什么反应。

变换 Transition:变换是在事件发生之后系统要做出的状态变化,如上面例子中的减速、停车或加速。

动作 Action:动作是同样是事件发生之后系统做出的反应,不同的是,动作不会改变系统状态,像驾车遇到红灯停车后,喝水这个动作没有对系统状态造成影响。

将状态机的四种要素提取之后,就可以很简单地将状态和事件进行解耦了。

2.2. 类图:

2.3. 状态对应不同的变换集合

状态-行进中:[刹车],

状态-关门:[开门,启动],

状态-开门:[关门],

状态-停运:[启动]

2.4. 这个关联关系是在哪里定义?

在状态机初始化时创建。

2.5. 事件驱动:

2.6. 代码示例:

2.6.1. 目录:

2.6.2. action包:

typescript 复制代码
/**
 * @author Bober
 * @date 2024/1/21 18:34
 */
public class Actions {

    /**
     * 执行方法
     * @param action
     * @return
     */
    public boolean doAction(SubwayAction action) {
        try {
            action.getActionCode().forEach(actionCode -> {
                switch (actionCode) {
                    case START_UP:
                        startUp(action.getAttributes());
                        break;
                    case CLOSING:
                        closing(action.getAttributes());
                        break;
                    case OPENING:
                        opening(action.getAttributes());
                        break;
                    case BRAKING:
                        braking(action.getAttributes());
                        break;
                    case DRINKING:
                        drinking(action.getAttributes());
                        break;
                    default:
                        closing(action.getAttributes());
                }
            });
        } catch (Exception e) {
            LogUtils.ERROR.error("doAction error...");
            return false;
        }
        return true;
    }

    private void drinking(Map<Object, Object> attributes) {
        System.out.println("执行关门操作...");
    }

    private void braking(Map<Object, Object> attributes) {
        System.out.println("执行刹车操作...");
    }

    private void opening(Map<Object, Object> attributes) {
        System.out.println("执行开门操作...");
    }

    private void closing(Map<Object, Object> attributes) {
        System.out.println("喝水歇歇...");
    }

    public void startUp(Map<Object, Object> req) {
        System.out.println("执行启动操作...");
    }
}

2.6.3. dto包:

swift 复制代码
/**
 * @author Bober
 * @date 2024/1/21 17:39
 */
@Data
public class SubwayAction {

    /**
     * 动作标识
     */
    private List<ActionCodeEnums> actionCode;

    /**
     * 附属的业务参数
     */
    private Map<Object, Object> attributes;

    public SubwayAction(List<ActionCodeEnums> actionCode) {
        this.actionCode = actionCode;
    }
}
typescript 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:11
 * 事件
 */
@Data
public class SubwayEvent {
    /**
     * 事件标识(编码)
     */
    private EventCodeEnums eventCode;

    /**
     * 附属的业务参数
     */
    private Map<Object, Object> attributes;
}
csharp 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:08
 * 状态
 */
@Data
public class SubwayState {
    /**
     * 状态编码
     */
    private StateCodeEnums stateCode;

    /**
     * 当前状态下可允许执行的变换
     */
    private List<SubwayTransition> transitions = new ArrayList<>();

    public SubwayState(StateCodeEnums stateCode, SubwayTransition... transitions) {
        this.stateCode = stateCode;
        this.transitions.addAll(Arrays.asList(transitions));
    }

    /**
     * 添加变换
     */
    public void addTransition(SubwayTransition transition) {
        transitions.add(transition);
    }
}

2.6.4. enums包:

typescript 复制代码
/**
 * @author Bober
 * @date 2024/1/21 18:07
 */
public enum ActionCodeEnums {
    /**
     * 动作枚举
     */
    START_UP("START_UP", "启动"),

    CLOSING("CLOSING", "关门"),

    OPENING("OPENING", "开门"),

    BRAKING("BRAKING", "刹车"),

    DRINKING("DRINKING", "喝水");

    private String code;

    private String displayName;

    ActionCodeEnums(String code, String displayName) {
        this.code = code;
        this.displayName = displayName;
    }

    public String getCode() {
        return code;
    }

    public String getDisplayName() {
        return displayName;
    }

    @Override
    public String toString() {
        return displayName;
    }
}
typescript 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:12
 * 类描述: 事件类型
 */
public enum EventCodeEnums {
    /**
     * 事件枚举
     */
    START_UP("START_UP", "启动"),
    CLOSING("CLOSING", "关门"),
    OPENING("OPENING", "开门"),
    BRAKING("BRAKING", "刹车");

    private String code;

    private String displayName;

    EventCodeEnums(String code, String displayName) {
        this.code = code;
        this.displayName = displayName;
    }

    public String getCode() {
        return code;
    }

    public String getDisplayName() {
        return displayName;
    }

    @Override
    public String toString() {
        return displayName;
    }
}
typescript 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:09
 * 类描述: 状态
 */
public enum StateCodeEnums {
    /**
     * 状态枚举
     */
    MOVING("MOVING", "行进中"),
    CLOSED("CLOSED", "到站-关门"),
    OPEN("OPEN", "到站-开门"),
    SUSPENDED("SUSPENDED", "停运的");

    private String code;

    private String displayName;

    StateCodeEnums(String code, String displayName) {
        this.code = code;
        this.displayName = displayName;
    }

    public String getCode() {
        return code;
    }

    public String getDisplayName() {
        return displayName;
    }

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

2.6.5. stateMachine包:

csharp 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:16
 */
public abstract class SubwayAbsStateMachine {
    /**
     * 定义的所有状态
     */
    private static List<SubwayState> allStates = null;

    /**
     * 状态机执行事件
     *
     * @param stateCode
     * @param event
     * @return
     */
    public SubwayState execute(StateCodeEnums stateCode, SubwayEvent event) {
        SubwayState startState = this.getState(stateCode);
        for (SubwayTransition transition : startState.getTransitions()) {
            if (event.getEventCode().equals(transition.getOn())) {
                //可将event中attributes转到action transition中...
                BeanUtil.copyProperties(event.getAttributes(), transition.getPerform().getAttributes());

                return transition.execute(transition.getPerform());
            }
        }
        LogUtils.ERROR.error("StateMachine[{}] Can not find transition for stateId[{}] eventCode[{}]", this.getClass().getSimpleName(), stateCode, event.getEventCode());
        System.out.println(String.format("StateMachine[%s] Can not find transition for current state:[%s] eventCode:[%s]", this.getClass().getSimpleName(), stateCode, event.getEventCode()));
        return null;
    }

    public SubwayState getState(StateCodeEnums stateCode) {
        if (allStates == null) {
            LogUtils.COMMON.info("StateMachine declareAllStates");
            allStates = this.declareAllStates();
        }

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

    /**
     * 由具体的状态机定义所有状态
     */
    public abstract List<SubwayState> declareAllStates();
}

核心状态机初始化:

scss 复制代码
/**
 * @author Bober
 * @date 2024/1/21 15:19
 */
public class SubwayStateMachine extends SubwayAbsStateMachine {
    @Override
    public List<SubwayState> declareAllStates() {
        // 定义状态机的状态
        List<SubwayState> stateList = new ArrayList<>();

        //进行中
        SubwayState movingState = new SubwayState(StateCodeEnums.MOVING);
        //关门
        SubwayState closedState = new SubwayState(StateCodeEnums.CLOSED);
        //开门
        SubwayState openState = new SubwayState(StateCodeEnums.OPEN);
        //停运
        SubwayState suspensionState = new SubwayState(StateCodeEnums.SUSPENDED);

        //进行中状态 触发【刹车事件】->到站-关门状态
        SubwayTransition closeTransition = BrakeTransition.builder()
                .from(movingState)
                .to(closedState)
                .perform(new SubwayAction(Arrays.asList(ActionCodeEnums.CLOSING, ActionCodeEnums.DRINKING))).build();
        movingState.addTransition(closeTransition);

        //到站-关门状态 触发【启动事件】->进行中
        SubwayTransition closeToMovingTransition = StartupTransition.builder()
                .from(closedState)
                .to(movingState)
                .perform(new SubwayAction(Collections.singletonList(ActionCodeEnums.START_UP))).build();
        closedState.addTransition(closeToMovingTransition);

        //到站-关门状态 触发【开门事件】->开门
        SubwayTransition closeToOpenTransition = StartupTransition.builder()
                .from(closedState)
                .to(openState)
                .perform(new SubwayAction(Collections.singletonList(ActionCodeEnums.OPENING))).build();
        closedState.addTransition(closeToOpenTransition);

        //到站-开门状态 触发【关门事件】->关门
        SubwayTransition openToCloseTransition = CloseTransition.builder()
                .from(openState)
                .to(closedState)
                .perform(new SubwayAction(Collections.singletonList(ActionCodeEnums.CLOSING))).build();
        openState.addTransition(openToCloseTransition);

        //停运状态 触发【启动事件】->进行中
        SubwayTransition suspensionToMovingTransition = StartupTransition.builder()
                .from(suspensionState)
                .to(movingState)
                .perform(new SubwayAction(Collections.singletonList(ActionCodeEnums.START_UP))).build();
        suspensionState.addTransition(suspensionToMovingTransition);

        Collections.addAll(stateList, movingState, closedState, openState, suspensionState);
        return stateList;
    }
}

2.6.6. transition包:

事件都包在各Transition类中

scala 复制代码
/**
 * 刹车相关
 *
 * @author Bober
 * @date 2024/1/21 16:01
 */
public class BrakeTransition extends SubwayTransition {
    /**
     * 变换
     */
    public BrakeTransition(SubwayState currState, SubwayState nextState, SubwayAction action) {
        super(EventCodeEnums.BRAKING, currState, nextState, action);
    }

    /**
     * 动作
     */
    @Override
    protected boolean doExecute(SubwayAction action) {
        Actions actions = new Actions();
        return actions.doAction(action);
    }

}
scala 复制代码
/**
 * 关门相关
 *
 * @author Bober
 * @date 2024/1/21 16:03
 */
public class CloseTransition extends SubwayTransition {
    /**
     * 变换
     */
    public CloseTransition(SubwayState currState, SubwayState nextState, SubwayAction action) {
        super(EventCodeEnums.CLOSING, currState, nextState, action);
    }

    /**
     * 动作
     */
    @Override
    protected boolean doExecute(SubwayAction action) {
        Actions actions = new Actions();
        return actions.doAction(action);
    }
}
scala 复制代码
/**
 * 开门相关
 *
 * @author Bober
 * @date 2024/1/21 16:04
 */
public class OpenTransition extends SubwayTransition {
    /**
     * 变换
     */
    public OpenTransition(SubwayState currState, SubwayState nextState, SubwayAction action) {
        super(EventCodeEnums.OPENING, currState, nextState, action);
    }

    /**
     * 动作
     */
    @Override
    protected boolean doExecute(SubwayAction action) {
        Actions actions = new Actions();
        return actions.doAction(action);
    }
}
scala 复制代码
/**
 * 定义"启动"变换
 *
 * @author Bober
 * @date 2024/1/21 16:02
 */
public class StartupTransition extends SubwayTransition {
    /**
     * 变换
     */
    public StartupTransition(SubwayState currState, SubwayState nextState, SubwayAction action) {
        super(EventCodeEnums.START_UP, currState, nextState, action);
    }

    /**
     * 动作
     */
    @Override
    protected boolean doExecute(SubwayAction action) {
        Actions actions = new Actions();
        return actions.doAction(action);
    }
}
csharp 复制代码
/**
 * 变换抽象类
 *
 * @author Bober
 * @date 2024/1/21 15:16
 */
@Data
@Builder
public abstract class SubwayTransition {
    /**
     * 触发事件
     */
    private EventCodeEnums on;

    /**
     * 触发当前状态
     */
    private SubwayState from;

    /**
     * 触发后状态
     */
    private SubwayState to;

    /**
     * 执行动作
     */
    private SubwayAction perform;


    protected SubwayTransition(EventCodeEnums eventCode, SubwayState currState, SubwayState nextState, SubwayAction action) {
        super();
        this.on = eventCode;
        this.from = currState;
        this.to = nextState;
        this.perform = action;
    }

    /**
     * 执行动作
     */
    public SubwayState execute(SubwayAction action) {
        System.out.printf("当前是:%s 状态,执行:%s 操作后,流转成:"%s" 状态。%n", from, on, to);
        if (this.doExecute(action)) {
            return this.to;
        } else {
            return null;
        }
    }

    /**
     * 执行动作的具体业务
     */
    protected abstract boolean doExecute(SubwayAction action);
}

2.6.7. 测试类:

java 复制代码
public class TestSubwayStateMachine {
    @Test
    public void test() {
        SubwayAbsStateMachine sm = new SubwayStateMachine();
        SubwayState state = sm.execute(StateCodeEnums.MOVING, new SubwayEvent(EventCodeEnums.BRAKING));
    }
}

2.7. 思考:

  1. 状态机模式的前提是 有限个状态,不适用无线个状态的场景;
  2. 每个状态都有特定的变换(transition)集合,由事件触发;
  3. 此处变换和动作易混淆,变换为状态流转,动作为状态流转期间做的事情;
  4. 可以把状态机模式和观察者模式进行比较,也是以事件驱动的,finitestatemachine2.stateMachine.SubwayAbsStateMachine#execute 可以看做监听程序,每个事件都由SubwayStateMachine注册事件监听程序。
  5. 什么场景适合使用状态机模式?
  • 有静态的状态,并且是有限的;
  • 业务逻辑围绕不同状态之间的流转切换来实现;
  • 状态之间的切换往往通过不同的事件来触发(事件驱动)。

参考:

状态模式相关:

有限状态机(Finite State Machine, FSM):

相关推荐
让学习成为一种生活方式3 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画9 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生31 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink44 分钟前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea
小冉在学习1 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论
代码之光_19802 小时前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi2 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet