The State Pattern allows an object to alter its behavior when its internal state changes.
The object will appear to change its class.
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 its states as individual classes,
each inheriting from a common interface or abstract class.
This will typically result in a greater number of classes in your design. Beside this disadvantage,
using explicit classes for each state the code becomes easier to understand, maintain and it is more flexible.
The following class diagram shows an example, where the concrete state classes implement an abstract state interface.
The Context gets its behavior by delegating applied actions 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.
This way we follow the principle: Encapsulate what varies (the state).
The principle: Open for extension but closed for modification is also coverd by the State Pattern.
Each state is closed for modification, and yet the Context is open for extension by adding new state classes.
The State and Strategy Patterns have the same class diagram, but they differ in
intent. The Strategy Pattern typically configures Context classes with a behavior or algorithm,
which can be done through composition during runtime.
State Pattern allows a Context to change its behavior as the state of the Context
State transitions can be controlled by the State classes or by the Context classes.
It is also possible for State classes to be shared among Context instances.
If we require common methods, that are shared across states,
we use an abstract state class. Otherwise it is possible to use an interface.
Using an abstract class has the benefit of allowing you to add methods to the abstract class later,
without breaking the concrete state implementations.
State Pattern Example
The following example shows a Gumball machine with the following states that will be represented by individual classes:
NoQuarterState - The start state where the user hasn’t inserted a quarter.
HasQuarterState - After inserting a quarter, we transition to this state.
SoldState - This state is reached ff a the user inserted a quarter and truns the crank.
SoldOutState - If all gumballs are sold or the machine hasn’t been filled, the machine transitions to this state.
First, we’re going to define a State interface that contains a method for every action in the Gumball Machine.
Now we are going to implement a State class for every state of the machine.
These classes will be responsible for the behavior of the machine (Context) when it is in the corresponding state.
This way we delegate the work to the individual state classes.
The first state that will implement the State interface is NoQuarterState:
Note that not all actions are apropriate for each state. For example,
turnCrank() or ejectQuarter() make no sense in the NoQuarterState.
Therefore the user will be only informed to insert a quarter and no transition takes place.
Like all states will, this state has a reference to the GumballMachine, the Context,
which is used to get and set new states. For example, to transition from this state NoQuarterState to HasQuarterState
when the user inserts a coin, the insertQuarter() method uses the getter and setter of the GumballMachine class
that is defined next:
The GumballMachine class instantiates all concrete states and provides all possible action methods to the user.
The action methods of this Context class delegate the work to the currently set state, which is stored in the state
member. Note that dispense() requires no action method because it is an internal action of the Gumball machine.
A user can’t ask the machine to dispense directly. Instead, dispense() is called on the state object inside the
turnCrank() action method.
The rest of the states are implemented next. Each state has a reference to the GumballMachine to transition it to
a different state. The following is the HasQuarterState:
The SoldState looks like this:
Most of the actions are inapropriate in this state except for the internal dispense() action.
In this method the machine releases a gumball and transitions to NoQuarterState or SoldOutState depending
on the gumball count of the machine.
The final state that needs to be implemented is SoldOutState:
In this state the machine changes its behavior only if it gets refilled.
How to use the GumballMachine is shown in the following test program:
The output of this test run is:
It is easy to extend the Gumball machine with a winner state that will release two gumballs 1 out of 10 times when the crank is turned.
To do this the GumballMachine class needs a new state member winner and add the getter method of this new state:
The Winner state is similar to the SoldState and looks like this:
To test the new Winner state we use the following test program:
This produce an output that is similar to the following, depending on your luck :)