Search code examples
javaenumstransitionstate-machine

how to do proper transitions between states in statemachine with provided example


i recently asked a question on how to code my statemachine in a clean way. normally i code for PLC`s so im no expert in java. i got an helpfull answer to model the states as an enum, and encapsulate the transition logic inside the enums, like this:

public enum State {

s00_StandBy {
    @Override
    public State nextState() {
        // if...
        return ...;
    }
},
s10_DetermineOperation {
    @Override
    public State nextState() {
        // if ...
        return ....;
    }
},

public abstract State nextState(); 
}

But i am having trouble implementing this myself. First thing is why cant i use certain conditions inside the enum cases?

example:

boolean resetBtn,startBtn;
State currentState;

//Transition logic
public enum State {
    //if state is s00_standBy and startBtn is true proceed to s10
    s00_StandBy {
        @Override
        public State nextState() {
        if(startBtn){ // Cant use startBtn here? how to use conditions from outside the enum case?
            return s10_DetermineOperation;
        }
         }   
    };
  
public abstract State nextState();
}

//Main
public void main() throws Exception {

//while in this state do stuff
if(currentState.equals(State.s00_StandBy)) {

//when done or condition is met go to next state
currentState.nextState();
}
}

second problem im up against is, how do you code the enum encapsulated transitions to have multiple conditions to proceed to a single new state? like normally i would program the transition sort of like this:

//state
if (currentState.equals(s10_DetermineOperation) && resetBtn = true) 
Or (currentState.equals(s20_normalOperation) && resetBtn=true)
Or (currentState.equals(s30_someOtherState) && resetBtn=true){ 
return state.s00_StandBy;}

but to my understanding wih the encapsulated enums you can only jump from a certain state to another and every transition have to be coded seperately? so you get this for the above example:

boolean resetBtn,startBtn;
State currentState;

//Transition logic
public enum State {
    //if state is s00_standBy and startBtn is true proceed to s10
    s10_DetermineOperation {
        @Override
        public State nextState() {
        if(startBtn){ // Cant use startBtn here? how to use conditions from outside the enum case?
            return s00_StandBy;
        }
         }   
    },
     s20_normalOperation {
        @Override
        public State nextState() {
        if(resetBtn){ // Cant use startBtn here? how to use conditions from outside the enum case?
            return s00_StandBy;
        }
         }   
    },
        s30_normalOperation {
        @Override
        public State nextState() {
        if(resetBtn){ // Cant use startBtn here? how to use conditions from outside the enum case?
            return s00_StandBy;
        }
         }   
    },
    
 
public abstract State nextState();
}

this gets out of hand fast when you have a lot of transitions, what am i doing wrong here?


Solution

  • There could be several solutions.

    Instead of only one method for doing the transition, you could have one for evvery trigger, buttons in your case.

    For example:

    public abstract State startButtonPressed();
    public abstract State resetButtonPressed();
    

    And implement this methods in every state.

    Another way, is to encapsulate the fields you need to access in a class, for example Context, an add it as an argument to the state transition method(s).

    public abstract State nextState(Context context);
    
    
    public State nextState(Context context) {
        if(context.startBtn){ 
            return s00_StandBy;
        }
    }
    

    Of course, both solutions can be combined.

    Edit

    An example using context:

    The context class:

    public class Context {
    
        private boolean bool1;
        private boolean bool2;
    
        public Context() {
        }
    
        public boolean isBool1() {
            return bool1;
        }
    
        public void setBool1(boolean bool1) {
            this.bool1 = bool1;
        }
    
        public boolean isBool2() {
            return bool2;
        }
    
        public void setBool2(boolean bool2) {
            this.bool2 = bool2;
        }
    }
    

    The enum:

    public enum State {
    
        s00_StandBy {
            @Override
            public State nextState(Context context) {
                if (context.isBool1()) {
                    return s99_otherState;
                } else {
                    return s20_someOtherState;
                }
            }
        },
    
        s20_someOtherState {
            @Override
            public State nextState(Context context) {
                if (context.isBool2()) {
                    return s99_otherState;
                } else {
                    return s00_StandBy;
                }
            }
        },
    
        s99_otherState {
            @Override
            public State nextState(Context context) {
                return s00_StandBy;
            }
        };
    
        public abstract State nextState(Context context);
    }
    

    An example main method:

    public class Main {
    
        public static void main(String... args) {
    
            Context context = new Context();
            State currentState = State.s00_StandBy;
    
            context.setBool1(true);
    
            currentState = currentState.nextState(context);
            System.out.println(currentState);
    
        }
    }