javastatestate-machinestate-pattern

State Machine approach for simple state transitions


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.


Solution

  • 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.