Design Pattern problem involving N states and transitions between them

Amit picture Amit · Jan 13, 2010 · Viewed 16.2k times · Source

I have a problem at hand and I am not getting which design pattern to use. The problem goes as such:

I have to build a system which has 'N' states and my system has to do a transition from any state to any other state depending on some conditions. Ex: On condition 1, movement from State 1 to 3 and on condition 2 from state 1 to 4.

Even the transition from one state to other state can be done on 2 or more different conditions.

For example, transition from State 1 to state 3 can be done when:
condition 1 : "Its a Sunday"
condition 2: "Its Raining"
condition 3: "Its Raining and Sunday"
In each condition, the processing at State 3 can be different.

I hope I was able to understand the problem legibly. Kindly help.

Thanks a lot

Answer

Anurag picture Anurag · Jan 14, 2010

Its clearly a case of a finite-state-machine but its better to combine the conditions rather than create a new condition for each combination. I didn't like the Java example for State pattern on Wikipedia as states know about other states which wouldn't make sense in a lot of scenarios. A transition table that keeps a track of the from state, applicable condition(s), and to state, helps take care of that problem.

My two cents for an object oriented-finite-state-machine. There are improvements you could do on the OO front, but it gets the idea across.

class Transition {
    State from;
    Set<Condition> conditions;
    State to;
}

class State {
    String state;
}

class Condition {
    String condition;
}

The state machine can be constructed with the above types. There is no error checking, but you could throw an exception or something if no next state is found for some conditions.

class StateMachine {
    List<Transition> transitions;
    State current;

    StateMachine(State start, List<Transition> transitions) {
        this.current = start;
        this.transitions = transitions;
    }

    void apply(Set<Condition> conditions) {
        current = getNextState(conditions);
    }

    State getNextState(Set<Condition> conditions) {
        for(Transition transition : transitions) {
            boolean currentStateMatches = transition.from.equals(current);
            boolean conditionsMatch = transition.conditions.equals(conditions);
            if(currentStateMatches && conditionsMatch) {
                return transition.to;
            }
        }
        return null;
    }
}

And a test run:

Edit: With some more transitions and new states based on your comment:

State one = new State("one");
State two = new State("two");
State three = new State("three");

Condition sunday = new Condition("Sunday");
Condition raining = new Condition("Raining");
Condition notSunday = new Condition("Not Sunday");
Condition notRaining = new Condition("Not Raining");

List<Transition> transitions = new ArrayList<Transition>();
transitions.add(one, new Set(sunday), three);
transitions.add(one, new Set(sunday), two); // <<--- Invalid, cant go to two and three
transitions.add(one, new Set(raining), three);
transitions.add(one, new Set(sunday, raining), three);
transitions.add(one, new Set(notSunday, notRaining), three);

StateMachine machine = new StateMachine(one, transitions);
System.out.print(machine.current); // "one"
machine.apply(new Set(sunday, raining));
System.out.print(machine.current); // "three

I had a bitter experience with using a state machine for a fairly large project. The problem was with composite states. Just like the composite condition you mentioned (sunday and raining), there could technically be composite states which could further be broken down into unit states. This may or may not be the case in your situation, but still worth mentioning. If that is the case, it's best to modify the classical finite state machine and use a set of states instead of a single state to represent the from and to states. If your N is large, this will help keep sanity levels intact. Think hotmail folders vs gmail tags. The transition table would then be presented as

Transition(Set<State> from, Set<Condition> conditions, Set<State> to)