This example is code that implements the behavior of a Gumball machine. The Main class is used to illustrate that the machine cannot dispense gumballs unless the prerequisites have been met, i.e., there is candy in the machine, money has been inserted into the machine and the handle turned. The first four steps in the Main class show the results of improperly attempting to get the candy and steps 5 through 8 show the behavior of the machine as the necessary operations required for the machine to dispense gumballs are performed. The result of running this code is shown in the ANT output.
public class Main { public static void main(String[] args) { GumballMachine machine = new GumballMachine(); System.out.println("1. turning handle: " + machine.turnHandle()); System.out.println("2. inserting money: " + machine.insertMoney()); System.out.println("3. turning handle: " + machine.turnHandle()); System.out.println("4. taking gumball: " + machine.takeGumballs()); System.out.println("5. adding gumballs: " + machine.addGumballs()); System.out.println("6. inserting money: " + machine.insertMoney()); System.out.println("7. turning handle: " + machine.turnHandle()); System.out.println("8. taking gumball: " + machine.takeGumballs()); } }
public class GumballMachine { GumballMachineState state; // constructor public GumballMachine() { state = new HasNoGumballsState(); } GumballMachine addGumballs() { state = state.addGumballs(); return this; } GumballMachine insertMoney() { state = state.insertMoney(); return this; } GumballMachine turnHandle() { state = state.turnHandle(); return this; } GumballMachine takeGumballs() { state = state.takeGumballs(); return this; } @Override public String toString() { return state.toString(); } }
public abstract class GumballMachineState { GumballMachineState addGumballs() { return this; } GumballMachineState insertMoney() { return this; } GumballMachineState turnHandle() { return this; } GumballMachineState takeGumballs() { return this; } }
public class HasNoGumballsState extends GumballMachineState { @Override HasNoMoneyState addGumballs() { return new HasNoMoneyState(); } @Override public String toString() { return "Please add gumballs (" + this.getClass().getName() + ")"; } }
public class HasNoMoneyState extends GumballMachineState { @Override HandleNotTurnedState insertMoney() { return new HandleNotTurnedState(); } @Override public String toString() { return "Please insert money (" + this.getClass().getName() + ")"; } }
public class HandleNotTurnedState extends GumballMachineState { @Override GumballsNotTakenState turnHandle() { return new GumballsNotTakenState(); } @Override public String toString() { return "Please turn handle (" + this.getClass().getName() + ")"; } }
public class GumballsNotTakenState extends GumballMachineState { @Override HasNoMoneyState takeGumballs() { return new HasNoMoneyState(); } @Override public String toString() { return "Please take gumballs (" + this.getClass().getName() + ")"; } }
statemachine> ant Buildfile: build.xml clean: [delete] Deleting directory /Users/greghelton/dev/src/java/StateMachine/build [delete] Deleting directory /Users/greghelton/dev/src/java/StateMachine/dist compile: [mkdir] Created dir: /Users/greghelton/dev/src/java/StateMachine/build [javac] Compiling 7 source files to /Users/greghelton/dev/src/java/StateMachine/build jar: [mkdir] Created dir: /Users/greghelton/dev/src/java/StateMachine/dist [jar] Building jar: /Users/greghelton/dev/src/java/StateMachine/dist/StateMachine.jar run: [java] 1. turning handle: Please add gumballs (HasNoGumballsState) [java] 2. inserting money: Please add gumballs (HasNoGumballsState) [java] 3. turning handle: Please add gumballs (HasNoGumballsState) [java] 4. taking gumball: Please add gumballs (HasNoGumballsState) [java] 5. adding gumballs: Please insert money (HasNoMoneyState) [java] 6. inserting money: Please turn handle (HandleNotTurnedState) [java] 7. turning handle: Please take gumballs (GumballsNotTakenState) [java] 8. taking gumball: Please insert money (HasNoMoneyState) BUILD SUCCESSFUL Total time: 0 seconds statemachine>
Note that this rather complex behavior has been implemented without a single conditional statement. Conditional logic can certainly be added if necessary. The turnHandle() method of HandleNotTurnedState could 'return this' under some conditions and, under other conditions, 'return new GumballsNotTakenState()'.
The abstract class GumballMachineState acts to block all changes of state. Each of the classes that extend GumballMachineState override one of its methods and return a new instance of a subclass of GumballMachineState. These subclasses allow the state of the Gumball machine to be changed.