用于简单状态转换的状态机方法

State Machine approach for simple state transitions

我正在考虑创建一个非常简单的状态机。我的状态机将包含以下 3 个状态:

public enum States {
    PENDING,
    ACTIVE,
    DONE
}

这里可能有多个转换+起始状态,具体来说:

初始状态:PENDING or ACTIVE 过渡:

  1. PENDING -> ACTIVE
  2. PENDING -> DONE
  3. ACTIVE -> DONE

我正在研究表示这些状态的方法以及控制转换的可能状态机。我研究了一种基于枚举的方法,例如 this one,但我也想向客户端公开状态转换,但我不确定这在这种方法中是否合理。

我也研究了其他技术,例如 State Pattern,但感觉对于这样一个简单的问题来说,这可能有点矫枉过正。

有人对满足此条件的简单状态机实现有任何建议吗?我什至在想一些基本的东西,比如使用转换 table 来存储转换并在其中封装一个状态概念,该概念将使用转换 table 来确定下一个可能的状态。

其中一个简单的变体是以 I want to transition from X to Y while applying this function 的形式保存转换。枚举非常适合枚举状态机中所有可能/有效的状态。我们需要一些东西来保持我们的状态转换——也许是 Map<StateType, StateType> ?但我们还需要某种 State 对象和一种修改它的方法——我们需要一个 Map<StateType, Map<StateType, Transition>>。请参阅下面的一些可以帮助您入门的编译示例代码。您可以随心所欲地公开 State 对象(也许让它不可变?)并即时添加转换。

import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;

class WhosebugQuestion57661787 {
    enum StateType {
        PENDING,
        ACTIVE,
        DONE
    }

    //Made the name explicit here to ease readability
    public interface Transition extends Function<State, State> { }

    public static class State {
        public StateType type;
        //TODO: some real data to manipulate, or make it immutable
        public Object data;
    }

    public static class StateMachine {
        private final Map<StateType, Map<StateType, Transition>> transitions =
                new EnumMap<>(StateType.class);
        private State state;

        public StateMachine(State initialState) {
            this.state = initialState;
            for (StateType value : StateType.values()) {
                transitions.put(value, new EnumMap<>(StateType.class));
            }
        }

        public void addTransition(
                StateType input,
                StateType output,
                Transition transition
        ) {
            //TODO: handle collisions? multiple transitions for a given 
            // output statetype seems like a strange use-case
            transitions.get(input).put(output, transition);
        }

        public void moveTo(StateType toType) {
            Transition transition = transitions.get(state.type).get(toType);
            if (transition == null) {
                //TODO: handle me
                throw new RuntimeException();
            }
            //transition should modify the states "type" too OR
            //you implement it here
            state = transition.apply(state);
        }

        public State getState() {
            return state;
        }
    }
}

如果您的 State 对象类型依赖于当前的 StateType

,您将不得不寻求更复杂/抽象的解决方案

如果您正在使用 Spring,您可以考虑 Spring Statemachine。 https://projects.spring.io/spring-statemachine/

我有一个我广泛使用的个人设计,我称之为 'pump'。您的状态机 class 有一个名为 'pump' 的函数,它会评估状态并相应地更新。每个状态评估可能需要来自外部源(控制器)的一些输入,例如用户或 AI。初始化状态机时需要这些对象,并且通常是抽象实现。然后,您添加应用程序可以覆盖以捕获事件的事件回调。这种方法的一个优点是 'pump' 方法可以从单线程或多线程系统执行。

机器构建完成后,您可以通过简单地永远调用泵并提供 return 随机值的控制器来轻松地进行单元测试。这实际上是一个 'monkey' 测试,以确保您的机器可以处理任何输入组合而不会崩溃。

那么在你的应用中你只需要根据情况提供合适的控制器即可。

下面是一个非常粗略的状态机,用于控制一个假设的骰子游戏。我省略了大部分细节,留下了方法的核心。请注意,Player.rollDice 的一种实现可能是一种等待用户按下按钮以推进游戏的阻塞方法。在这个方案中,控制游戏的所有逻辑都包含在机器中,并且可以独立于任何 UI.

进行测试
interface Player {
   boolean rollDice();
}

class Game {
   int state;
   Player [] players;
   int currentPlayer;
   int dice;

   void pump() {
      switch (state) {
         case ROLL_DICE:
            if (players[currentPlayer].rollDice()) {
               dice = Math.rand() % 6 + 1;
               onDiceRolled(dice);
               state = TAKE_TURN;
            }
            break;
         case TAKE_TURN:
            ...
            break;
      }
   }

   // base method does nothing. Users can override to handle major state transitions.
   protected void onDiceRolled(int dice) {}
}