Object-Oriented Design – Implementing Object State in OO Languages

designdesign-patternsobject-orientedswitch statement

I've been given some Java code to look at, which simulates a car race, of which includes an implementation of a basic state machine. This is not a classical computer science state machine, but merely an object that can have multiple states, and can switch between its states based on a series of calculations.

To describe just the problem, I've got a Car class, with a nested enum class which defines some constants for the state of the Car (such as OFF, IDLE, DRIVE, REVERSE, etc). Within this same Car class I have an update function, which basically consists of a large switch statement which switches on the cars current state, does some calculations and then changes the cars state.

As far as I can see, the Cars state is only used within its own class.

My question is, is this the best way of dealing with the implementation of a state machine of the nature described above? It does sound like the most obvious solution, but in the past I've always heard that "switch statements are bad".

The main problem I can see here is that the switch statement could possibly become very large as we add more states (if deemed necessary) and the code could become unwieldy and hard to maintain.

What would be a better solution to this problem?

Best Answer

  • I turned the Car into a state machine of sorts using State Pattern. Notice no switch or if-then-else statements are used for state selection.

  • In this cases all states are inner classes but it could be implemented otherwise.

  • Each state contains the valid states it can change to.

  • User is prompted for the next state in case more than one is possible, or simply to confirm in case only one is possible.

  • You can compile it and run it to test it.

  • I used a graphic dialog box because it was easier that way to run it interactively in Eclipse.

enter image description here

The UML diagram is taken from here.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JOptionPane;

public class Car {

    private State state;
    public static final int ST_OFF=0;
    public static final int ST_IDDLE=1;
    public static final int ST_DRIVE=2;
    public static final int ST_REVERSE=3;

    Map<Integer,State> states=new HashMap<Integer,State>();

    public Car(){
        this.states.put(Car.ST_OFF, new Off());
        this.states.put(Car.ST_IDDLE, new Idle());
        this.states.put(Car.ST_DRIVE, new Drive());
        this.states.put(Car.ST_REVERSE, new Reverse()); 
        this.state=this.states.get(Car.ST_OFF);
    }

    private abstract class State{

        protected List<Integer> nextStates = new ArrayList<Integer>();

        public abstract void handle();
        public abstract void change();

        protected State promptForState(String prompt){
            State s = state;
            String word = JOptionPane.showInputDialog(prompt);
            int ch = -1;
            try {
                ch = Integer.parseInt(word);
            }catch (NumberFormatException e) {
            }   

            if (this.nextStates.contains(ch)){
                s=states.get(ch);
            } else {
                System.out.println("Invalid option");
            }
            return s;               
        }       

    }

    private class Off extends State{

        public Off(){ 
            super.nextStates.add(Car.ST_IDDLE);             
        }

        public void handle() { System.out.println("Stopped");}

        public void change() {
            state = this.promptForState("Stopped, iddle="+Car.ST_IDDLE+": ");
        }

    }

    private class Idle extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Idle(){
            super.nextStates.add(Car.ST_DRIVE);
            super.nextStates.add(Car.ST_REVERSE);
            super.nextStates.add(Car.ST_OFF);       
        }

        public void handle() {  System.out.println("Idling");}

        public void change() { 
            state=this.promptForState("Idling, enter 0=off 2=drive 3=reverse: ");
        }

    }

    private class Drive extends State{

        private List<Integer> nextStates = new ArrayList<Integer>();
        public Drive(){
            super.nextStates.add(Car.ST_IDDLE);
        }       
        public void handle() {System.out.println("Driving");}

        public void change() {
            state=this.promptForState("Idling, enter 1=iddle: ");
        }       
    }

    private class Reverse extends State{
        private List<Integer> nextStates = new ArrayList<Integer>();
        public Reverse(){ 
            super.nextStates.add(Car.ST_IDDLE);
        }           
        public void handle() {System.out.println("Reversing");} 

        public void change() {
            state = this.promptForState("Reversing, enter 1=iddle: ");
        }       
    }

    public void request(){
        this.state.handle();
    }

    public void changeState(){
        this.state.change();
    }

    public static void main (String args[]){
        Car c = new Car();
        c.request(); //car is stopped
        c.changeState();
        c.request(); // car is iddling
        c.changeState(); // prompts for next state
        c.request(); 
        c.changeState();
        c.request();    
        c.changeState();
        c.request();        
    }

}
Related Topic