用于简单状态转换的状态机方法
State Machine approach for simple state transitions
我正在考虑创建一个非常简单的状态机。我的状态机将包含以下 3 个状态:
public enum States {
PENDING,
ACTIVE,
DONE
}
这里可能有多个转换+起始状态,具体来说:
初始状态:PENDING or ACTIVE
过渡:
PENDING -> ACTIVE
PENDING -> DONE
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) {}
}
我正在考虑创建一个非常简单的状态机。我的状态机将包含以下 3 个状态:
public enum States {
PENDING,
ACTIVE,
DONE
}
这里可能有多个转换+起始状态,具体来说:
初始状态:PENDING or ACTIVE
过渡:
PENDING -> ACTIVE
PENDING -> DONE
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) {}
}