This week we’ll continue our exploration of design patterns and look at a commonly used behavioural pattern, the State pattern.
The State Pattern
The Gang of Four book defines the State pattern as follows:
“Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.”
State Design Pattern
Think of a zone controller for a home irrigation system. It would have a number of states: disabled, enabled, idle, checking (for moisture level) and watering (the sprinklers are on). The ZoneController
would move through a number of states as events get triggered in the system. Its behaviour is dependent on its current state. How would we actually implement those states in code?
State machines are usually implemented with lots of conditional statements (if..else
or switch..case
) that select the appropriate behaviour depending on the current state of the object. Usually, this “state” is just a set of values of the object’s fields.
We can handle these different states inside the ZoneController
by boolean
flags representing each state. Handling the combinations of flags can become difficult when we are developing, modifying or maintaining the code. Any later use case or logic changes will cause changes to the conditional statements, often in many places in the code.
A better solution is to use the State design pattern. The state pattern defines a context class which is the ZoneController
in this case. The context doesn’t implement the state logic internally, but rather delegates all the state-related work to a state object. We create new classes for each of the possible states of the context, and encapsulate the state-specific behaviours in these classes. We swap in an object of the correct state when the state changes.
The context stores a reference to a state object that represents its current state. As the states change, we replace the active state object with another object that represents the new state. All state classes implement the same interface so the context can use them interchangeably.
Requests that are state-dependent are forwarded to the current state object. Requests that are independent of state are handled directly in the context. Either the context or the concrete state objects are responsible for transitioning from one state to another.
Example Code
Back in our first person shooter game, we’ve decided that players should be able to buy Robot
assistants that will help carry weapons and other supplies. A Robot
will even be able to fight alongside the player if they’re not overloaded with supplies. This will be an upgrade option in a more advanced level of the game.
These Robot
assistants will be battery powered, so they will have to be charged occasionally. Nuclear powered robots would be a nice option, but if their reactors are damaged they could explode, wreaking havoc on the game.
We can imagine that the Robot
could be in a number of states: idle, charging, following, carrying, fighting, etc. We could model these states with a number of conditional statements (if..else
or switch..case
) that select the appropriate behaviour depending on the current state of the Robot
.
public class Robot { // fields for states private boolean isIdle; private boolean isCharging; private boolean isCarrying; private boolean isFighting; // methods with lots of clumsy conditional statements } // end of class
As we’ve previously mentioned, this becomes very clumsy and difficult to modify and maintain.
Let’s rather model the states as a separate class.
public abstract class RobotState { // default behaviour for all methods is to signal an error. public void idle (Robot robot) { error(); } public void charge(Robot robot) { error(); } public void carry (Robot robot) { error(); } public void fight (Robot robot) { error(); } private void error() { // some error signalling } } // end of class
The Robot
class now becomes simpler with no conditional code. All state logic is delegated to the state objects.
class Robot { // field for state private RobotState state; public Robot() { state = new IdleState(); } // methods with default behaviour public void idle() { state.idle(this); } public void charge() { state.charge(this); } public void carry() { state.carry(this); } public void fight() { state.fight(this); } // package visibility so only classes in the // same package can trigger a state change. void changeState(RobotState state) { this.state = state; } } // end of class
The conditional statements to check whether the Robot
can move from one state to another will be done in the specific concrete RobotState
classes. The IdleState
class follows:
public class IdleState extends RobotState { public void idle(Robot robot) { System.out.println("Already idling"); } public void charge(Robot robot) { System.out.println("Moving to charge state..."); robot.changeState(new ChargeState()); } public void carry(Robot robot) { System.out.println("Moving to carry state..."); robot.changeState(new CarryState()); } public void fight(Robot robot) { System.out.println("Moving to fight state..."); robot.changeState(new FightState()); } } // end of class
State Transitions
We need to think about which class should be responsible for state transitions. Should it be the context class (the Robot
in this example) or the actual state objects (IdleState
, ChargeState
, etc.)?
- If the state transitions are independent of each other, then we would make the context responsible for state transitions.
- If state transitions occur when events are received in other states, then we should put the state transition logic in the state objects themselves. The individual state objects (subclasses of
RobotState
) are responsible for state transitions.
The coupling between state objects is increased if they have the responsibility for the state transitions. The previous state must know about the next state. However, this usually results in code that is easier to understand and maintain.
State Pattern vs Strategy Pattern
The class structure of the State pattern and the Strategy pattern are identical. The key difference is intent; the patterns have different intents. The intent of the State pattern is to allow an object to change its behaviour when its internal state changes. The intent of the Strategy pattern is to encapsulate different algorithms and make them interchangeable.
Another difference between the two patterns is that a strategy is normally chosen once at instantiation time and is used for the duration of the application, while states are changed regularly during the running of an application. In the State pattern, the particular states are often aware of each other and they can initiate transitions from one state to another, while strategies almost never know about each other.
The State pattern implies a Strategy that changes frequently over time. Strategy sets an object’s behaviour at construction time.
Pros and Cons
Using the State pattern has a lot of benefits:
- It follows the Single Responsibility principle. We model each individual state as a separate class with its own responsibility.
- It conforms to the Open-Closed principle. We can create new state classes without changing any of the existing state classes or the context itself, as long as those classes all implement the same interface.
- It simplifies the code of the context class by removing complicated state machine conditional statements.
However, using the pattern can be overkill if a state machine has only a few states or the states rarely change.
What’s next?
In the weeks ahead, we’ll continue examining some of the more useful design patterns. Stay tuned!
As always, please share your comments and questions.