BIM492 DESIGN PATTERNS
10. STATE PATTERN
A little known fact:
Strategy and State Patterns
were twins separated at birth
Jaw Breakers
Gumball machines have gone high tech; the
major manufacturers have found that by
putting CPUs into their machines, they can
increase sales, monitor inventory over the
network and measure customer
satisfaction more accurately.
2
But these manufacturers
are gumball machine
experts, not software
developers, and they’ve
asked for your help:
3
How are we going to get from that state diagram to actual code?
State machines 101 Here’s a quick introduction to implementing state machines:
1. First, gather up your states:
2. Next, create an instance variable to hold the current state, and define values for each of the states:
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
5
3. Now we gather up all the actions that can happen in the system:
4. Now we create a class that acts as the state machine. For each action, we create a method that uses
conditional statements to determine what behavior is appropriate in each state. For instance, for the
insert quarter action, we might write a method like this:
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println(“You can’t insert another quarter”);
} else if (state == SOLD_OUT) {
System.out.println(“You can’t insert a quarter, the machine is sold out”);
} else if (state == SOLD) {
System.out.println(“Please wait, we’re already giving you a gumball”);
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println(“You inserted a quarter”);
}
}
6
Writing the code
It’s time to implement the Gumball Machine.
We know we’re going to have an instance variable that holds the current
state. From there, we just need to handle all the actions, behaviors and
state transitions that can happen.
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}
7
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println(“You can’t insert another quarter”);
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println(“You inserted a quarter”);
} else if (state == SOLD_OUT) {
System.out.println(“You can’t insert a quarter, the machine is sold out”);
} else if (state == SOLD) {
System.out.println(“Please wait, we’re already giving you a gumball”);
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println(“Quarter returned”);
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println(“You haven’t inserted a quarter”);
} else if (state == SOLD) {
System.out.println(“Sorry, you already turned the crank”);
} else if (state == SOLD_OUT) {
System.out.println(“You can’t eject, you haven’t inserted a quarter yet”);
}
}
8
public void turnCrank() {
if (state == SOLD) {
System.out.println(“Turning twice doesn’t get you another gumball!”);
} else if (state == NO_QUARTER) {
System.out.println(“You turned but there’s no quarter”);
} else if (state == SOLD_OUT) {
System.out.println(“You turned, but there are no gumballs”);
} else if (state == HAS_QUARTER) {
System.out.println(“You turned...”);
state = SOLD;
dispense();
}
}
public void dispense() {
if (state == SOLD) {
System.out.println(“A gumball comes rolling out the slot”);
count = count - 1;
if (count == 0) {
System.out.println(“Oops, out of gumballs!”);
state = SOLD_OUT;
} else { state = NO_QUARTER; }
} else if (state == NO_QUARTER) {
System.out.println(“You need to pay first”);
} else if (state == SOLD_OUT) {
System.out.println(“No gumball dispensed”);
} else if (state == HAS_QUARTER) {
System.out.println(“No gumball dispensed”);
}
}
// other methods here like toString() and refill() }
9
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
} 10
You knew it was coming... a change request!
Mighty Gumball, Inc. has loaded your code into their newest
machine and their quality assurance experts are putting
it through its paces. So far, everything’s looking great from
their perspective.
In fact, things have gone so smoothly they’d like to take
things to the next level...
11
Design Puzzle
Draw a state diagram for a Gumball
Machine that handles the 1 in 10
contest.
In this contest, 10% of the time the
Sold state leads to two balls being
released, not one.
12
Design Puzzle
Solution
13
The messy STATE of things...
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
public void insertQuarter() {
}
// insert quarter code here
Sharpen your pencil
public void ejectQuarter() {
// eject quarter code here
Which of the following describe the state of our
}
implementation? (Choose all that apply.)?
public void turnCrank() { A. This code certainly isn’t adhering to the Open Closed
// turn crank code here Principle.
}
public void dispense() { B. This design isn’t even very object oriented.
// dispense code here C. State transitions aren’t explicit; they are buried in the
} middle of a bunch of conditional statements.
D. We haven’t encapsulated anything that varies here.
E. Further additions are likely to cause bugs in working
code.
14
The new design
It looks like we’ve got a new plan: instead of maintaining our existing code, we’re going to rework it to
encapsulate state objects in their own classes and then delegate to the current state when an action occurs.
We’re following our design principles here, so we should end up with a design that is easier to maintain down
the road. Here’s how we’re going to do it:
1. First, we’re going to define a State interface that
contains a method for every action in the Gumball
Machine.
2. Then we’re going to implement a State class for every
state of the machine. These classes will be responsible
for the behavior of the machine when it is in the
corresponding state.
3. Finally, we’re going to get rid of all of our conditional
code and instead delegate to the state class to do the
work for us. 16
Defining the State interfaces and classes
First let’s create an interface for State, which all
our states implement:
Then take each state in our design
and encapsulate it in a class that
implements the State interface.
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
17
Sharpen your pencil
To implement our states, we first need
to specify the behavior of the classes
when each action is called. Annotate the
diagram below with the behavior of each
action in each class; we’ve already filled
in a few for you.
18
Time to implement a state: we know what behaviors we want; we just need
Implementing our State classes to get it down in code. We’re going to closely follow the state machine code
we wrote, but this time everything is broken out into different classes.
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println(“You inserted a quarter”);
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println(“You haven’t inserted a quarter”);
}
public void turnCrank() {
System.out.println(“You turned, but there’s no quarter”);
}
public void dispense() {
System.out.println(“You need to pay first”);
}
} 19
Reworking the Gumball Machine
Before we finish the State classes, we’re going to rework the Gumball
Machine – that way you can see how it all fits together. We’ll start with
the state-related instance variables and switch the code from using
integers to using state objects:
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT; public class GumballMachine {
int count = 0; State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;
20
public class GumballMachine {
State soldOutState;
State noQuarterState;
Now, let’s look at the complete
State hasQuarterState;
State soldState; GumballMachine class...
State state = soldOutState;
int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println(“A gumball comes rolling out the slot...”);
if (count != 0) {
count = count - 1;
}
} // More methods here including getters for each State...
} 21
public class HasQuarterState implements State {
GumballMachine gumballMachine; Implementing
public HasQuarterState(GumballMachine gumballMachine) { more states
this.gumballMachine = gumballMachine; Now that you’re starting to get a feel
}
for how the Gumball Machine and the
public void insertQuarter() { states fit together, let’s implement the
System.out.println(“You can’t insert another quarter”); HasQuarterState and the
} SoldState classes...
public void ejectQuarter() {
System.out.println(“Quarter returned”);
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println(“You turned...”);
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println(“No gumball dispensed”);
}
} 22
Now, let’s check out the SoldState class...
public class SoldState implements State {
//constructor and instance variables here
public void insertQuarter() {
System.out.println(“Please wait, we’re already giving you a gumball”);
}
public void ejectQuarter() {
System.out.println(“Sorry, you already turned the crank”);
}
public void turnCrank() {
System.out.println(“Turning twice doesn’t get you another gumball!”);
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println(“Oops, out of gumballs!”);
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
} 23
public class SoldOutState implements State
GumballMachine gumballMachine;
{
Sharpen your pencil
public SoldOutState(GumballMachine gumballMachine) { We have one remaining class we haven’t implemented:
this.gumballMachine = gumballMachine; SoldOutState. Why don’t you implement it? To do
} this, carefully think through how the Gumball Machine
should behave in each situation.
public void insertQuarter() {
System.out.println(“You can’t insert a quarter, the machine is sold out”);
}
public void ejectQuarter() {
System.out.println(“You can’t eject, you haven’t inserted a quarter yet”);
}
public void turnCrank() {
System.out.println(“You turned, but there are no gumballs”);
}
public void dispense() {
System.out.println(“No gumball dispensed”);
}
}
24
Let’s take a look at what we’ve done so far...
For starters, you now have a Gumball Machine
implementation that is structurally quite different from your
first version, and yet functionally it is exactly the same.
By structurally changing the implemention you’ve:
Localized the behavior of each state into its own
class.
Removed all the troublesome if statements that
would have been difficult to maintain.
Closed each state for modification, and yet left the
Gumball Machine open to extension by adding new
state classes (and we’ll do this in a second).
Created a code base and class structure that maps
much more closely to the Mighty Gumball diagram
and is easier to read and understand.
25
26
The State Pattern defined
The first part of this description makes a lot of
sense, right? Because the pattern encapsulates
state into separate classes and delegates to the
object representing the current state, we know
that behavior changes along with the internal state.
What about the second part of the definition?
What does it mean for an object to “appear to
change its class?”
Think about it from the perspective of a client: if
an object you’re using can completely change its
behavior, then it appears to you that the object is
actually instantiated from another class.
In reality, however, you know that we are using
composition to give the appearance of a class
change by simply referencing different state
objects.
27
You’ve got a good eye! Yes, the class diagrams are essentially the
same, but the two patterns differ in their intent.
With the State Pattern, we have a set of behaviors encapsulated in state
objects; at any time the context is delegating to one of those states.
Over time, the current state changes across the set of state objects to
reflect the internal state of the context, so the context’s behavior
changes over time as well. The client usually knows very little, if
anything, about the state objects.
With Strategy, the client usually specifies the strategy object that the
context is composed with. Now, while the pattern provides the flexibility
to change the strategy object at runtime, often there is a strategy
object that is most appropriate for a context object.
In general, think of the Strategy Pattern as a flexible alternative to subclassing; if you
use inheritance to define the behavior of a class, then you’re stuck with that behavior
even if you need to change it. With Strategy you can change the behavior by composing
with a different object.
Think of the State Pattern as an alternative to putting lots of conditionals in your
context; by encapsulating the behaviors within state objects, you can simply change
the state object in context to change its behavior.
28
We still need to finish the Gumball 1 in 10 game
First, we need to add a state to the GumballMachine class --> publicclass GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
Now let’s implement the WinnerState class itself, it’s remarkably similar
State soldState;
to the SoldState class: State winnerState;
public class WinnerState implements State {
// instance variables and constructor
// insertQuarter error message
State state = soldOutState;
// ejectQuarter error message int count = 0;
// turnCrank error message // methods here
public void dispense() { }
System.out.println(“YOU’RE A WINNER! You get two gumballs for your quarter”);
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println(“Oops, out of gumballs!”);
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
} 29
We’ve just got one more change to make: we need to implement the random chance
Finishing the game game and add a transition to the WinnerState. We’re going to add both to the
HasQuarterState since that is where the customer turns the crank:
public class HasQuarterState implements State {
Random randomWinner = new Random(System.currentTimeMillis());
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println(“You can’t insert another quarter”);
}
public void ejectQuarter() {
System.out.println(“Quarter returned”);
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println(“You turned...”);
int winner = randomWinner.nextInt(10); Wow, that was pretty simple to implement!
if ((winner == 0) && (gumballMachine.getCount() > 1)) { We just added a new state to the
gumballMachine.setState(gumballMachine.getWinnerState());
} else { GumballMachine and then implemented it.
gumballMachine.setState(gumballMachine.getSoldState());
} All we had to do from there was to
}
public void dispense() {
implement our chance game and transition
System.out.println(“No gumball dispensed”); to the correct state. It looks like our new
} code strategy is paying off...
}
30
Demo for the CEO of Might y Gumball, Inc.
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
31
32
35
Tools for your Design Toolbox
Bullet Points
The State Pattern allows an object to have many different behaviors
that are based on its internal state.
Unlike a procedural state machine, the State Pattern represents state as
a full-blown class.
The Context gets its behavior by delegating to the current state object
it is composed with.
By encapsulating each state into a class, we localize any changes that
will need to be made.
The State and Strategy Patterns have the same class diagram, but they
differ in intent.
Strategy Pattern typically configures Context classes with a behavior or
algorithm.
State Pattern allows a Context to change its behavior as the state of
the Context changes.
State transitions can be controlled by the State classes or by the
Context classes.
Using the State Pattern will typically result in a greater number of
classes in your design.
State classes may be shared among Context instances.
36