2

I am looking into creating a very simple state machine. My state machine will contain the following 3 states:

public enum States {
    PENDING,
    ACTIVE,
    DONE
}

There are multiple transitions + starting states that are possible here, specifically:

Initial States: PENDING or ACTIVE Transitions:

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

I'm looking into approaches to represent these states and a possible state machine to control the transitions. I've looked into an enum based approach such as this one, but I also want to expose state transitions to the client and I'm unsure if this is reasonable in this approach.

I've also looked at other techniques such as the State Pattern, but it feels like this may be overkill for such a simple ask.

Does anyone have any suggestions for simple state machine implementations that meet this criteria? I was even thinking something as basic as using a transition table to store the transitions and encapsulating a state concept within it that would use the transition table to determine next possible states.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
thebighoncho
  • 385
  • 2
  • 6
  • 20

4 Answers4

2

One of the simple variants is to save transitions in a form of I want to transition from X to Y while applying this function. Enums make a good fit to enumerate all possible / valid states in a state machine. We need something to hold on to our state transitions - maybe a Map<StateType, StateType> ? But we also need some sort of State object and a way to modify it - we need a Map<StateType, Map<StateType, Transition>>. See below for some compiling sample code that should get you started. You can expose the State object however you like (maybe make it immutable?) and add transitions on the fly.

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

class StackOverflowQuestion57661787 {
    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;
        }
    }
}

You will have to reach for a more sophisticated / abstracted solution if your State objects type is dependent on the current StateType.

roookeee
  • 1,710
  • 13
  • 24
1

If you are using Spring you can consider Spring Statemachine. https://projects.spring.io/spring-statemachine/

charlb
  • 1,076
  • 9
  • 18
0

I have a personal design that I have used extensively that I call the 'pump'. Your state machine class has a function called 'pump' which evaluates the state and updates accordingly. Each state evaluation might require some input from an external source (controllers), like the user or an AI. These objects are required when initializing your state machine and are typically abstract implementations. You then add event callbacks that applications can override to catch events. One advantage to this approach the 'pump' method can be executed from a single or multi-thread system.

Once your machine is built you can unit test easily by simply calling pump forever and providing controllers that return random values. This would effectively be a 'monkey' test to make sure your machine can handle any combination of inputs without crashing.

Then in your application you need only provide the proper controllers based on the situation.

Below is a very rough state machine to control a hypothetical dice game. I omitted most details leaving the meat of the approach. Notice that one implementation of Player.rollDice could be a blocking method that waits for the user to hit a button to advance the game. In this scheme all of the logic to control the game is contained in the machine and can be tested independently of any 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) {}
}
Fracdroid
  • 1,135
  • 10
  • 15
0

I would also advice you to check two frameworks before you implement your own State Machine. State Machine theory is really complex to develop all by yourself, specially not too much mentioned concepts like Sub / Nested State Machines are a must for complex / successful State Machine designs.

One is mentioned above Spring State Machine and second is the Akka Finite State Machine.

My personal experience Spring State Machine is great for modelling things like a lifecycle of an application with states like, STARTING, INITIALISING, RUNNING, MAINTENANCE, ERROR, SHUTDOWN, etc.... but it is not that great for modelling things like Shopping Charts, Reservation, Credit Approval Processes, etc... while it has too big memory footprint to model millions of instances.

In the other hand, Akka FSM has a real small footprint and I personally implemented systems containing millions of instances of the State Machine and it has another tool that completely missing in Spring State Machine. In modern IT one thing is inevitable, change, no workflow / process that you model, will not stay same over the time, so you need mechanism to integrate these changes to your long running workflows / processes (what I mean with that, what happens if you have process that started before your latest software release and persisted with old model, now you have a new release and the model is changed, you have to read persisted process and continue with the new model). Akka a built in solution for this problem Event / Schema Evolution.

If you need examples about how the implementations of Spring State Machine you can check the following Blog of me, for Akka FSM examples you can check the following examples Blog1, Blog2.

I hope this would help.

posthumecaver
  • 1,584
  • 4
  • 16
  • 29