Overview
The State design pattern is a behavioral pattern that allows you to implement Finite-State Machine using the Object Oriented Approach.
When a behavior or a business process is described using Finite-State Machine, usually expected behavior and available actions, transitions depend on the current state. The State design pattern offers a structure that allows you to capture state-dependent behaviors in an Object Oriented way. It allows an object to change its behavior when its internal state changes. It involves encapsulating state-specific behaviors into a set of interchangeable state classes. The object delegates state-specific behaviors to a state object. All state objects are represented by a class that implements a common interface. The common interface for state classes allows the main object to switch and delegate state-specific behaviors to the state object. At each time, the object can exist in one of the finite states and can transition between states. Behavior is dependent on the current state. The object usually starts with some initial state, which can be changed during the object lifecycle, which changes its behavior. States can be aware of each other. One state may switch the object state to the next one. Switching the object state is equivalent to transitions in a Finite-State Machine.
Use Cases
The State design pattern is useful when an object has multiple behaviors that need to be switched dynamically depending on the object’s state. In addition, the pattern is handy when an object’s behavior is complex and difficult to implement using conditional statements to represent state-specific behaviors under each condition.
Potential use cases for the State design pattern involve:
- Document Editor: A document editor could use the State pattern to manage the different states of the document, such as editing mode, read-only mode, and print preview mode. Each state would have its rules and behaviors that define how the document behaves, such as which buttons are available and what happens when the user tries to edit the document in read-only mode.
- Online Shopping Cart: An online shopping cart could use the State pattern to manage the different states of the cart, such as empty and items added. Each state would have its rules and behaviors that define how the cart behaves, such as whether certain buttons are disabled or enabled depending on the current state.
- Payment System: Payment can exist in multiple states, like initiated, in progress, waiting for confirmation, completed, or failed. Each state defines rules on data, actions, and the next possible state.
- Music Player: A music player could use the State pattern to manage the different states of the player, such as playing, paused, and stopped. Each state would have its rules and behaviors that define how the player behaves, such as what happens when the user tries to pause the song while the player is stopped.
- Game Development: A game engine could use the State pattern to manage the different states of the character in the game. The character could exist in multiple states, like standing, walking, or running. Each state is associated with different behavior.
Structure
The State design pattern consists of the following components:
- Context: The object whose behavior changes based on its internal state.
- State: The interface that defines the object’s behavior based on its state.
- ConcreteState: The implementation of the State interface that defines the object’s behavior for a specific state. States can be aware of each other. One state may switch the context object state to the next one. The state is aware of the context object to initiate state transition.
Here’s the UML diagram of the State pattern:
Example Code
In this example code, we will create Object Oriented code that will implement the below State Machine:
The below example will use the following components:
- Context: will be implemented by
GameCharacter
class - State: will be implemented by
CharacterState
interface - ConcreteStates: will be implemented by following classes:
StandingCharacterState
WalkingCharacterState
RunningCharacterState
First, let’s create an interface that will represent all possible states:
public interface CharacterState {
String getName();
void move();
void speak();
void shoot();
void toNextState();
}
Now, let’s create a class that will encapsulate Game Character behavior for each state.
Standing state:
public class StandingCharacterState implements CharacterState {
private final GameCharacter gameCharacter;
public StandingCharacterState(GameCharacter gameCharacter) {
this.gameCharacter = gameCharacter;
}
@Override
public String getName() {
return "Standing";
}
@Override
public void move() {
System.out.println("Standing...");
}
@Override
public void speak() {
System.out.println("Speaking while standing...");
}
@Override
public void shoot() {
System.out.println("Shooting while standing...");
}
@Override
public void toNextState() {
gameCharacter.setCharacterState(new WalkingCharacterState(gameCharacter));
}
}
Walking state:
public class WalkingCharacterState implements CharacterState {
private final GameCharacter gameCharacter;
public WalkingCharacterState(GameCharacter gameCharacter) {
this.gameCharacter = gameCharacter;
}
@Override
public String getName() {
return "Walking";
}
@Override
public void move() {
System.out.println("Walking...");
}
@Override
public void speak() {
System.out.println("Speaking while walking...");
}
@Override
public void shoot() {
System.out.println("Shooting while walking...");
}
@Override
public void toNextState() {
gameCharacter.setCharacterState(new RunningCharacterState(gameCharacter));
}
}
Running state:
public class RunningCharacterState implements CharacterState {
private final GameCharacter gameCharacter;
public RunningCharacterState(GameCharacter gameCharacter) {
this.gameCharacter = gameCharacter;
}
@Override
public String getName() {
return "Running";
}
@Override
public void move() {
System.out.println("Running...");
}
@Override
public void speak() {
System.out.println("Speaking while running...");
}
@Override
public void shoot() {
System.out.println("Shooting while running...");
}
@Override
public void toNextState() {
gameCharacter.setCharacterState(new StandingCharacterState(gameCharacter));
}
}
Now, we will create a GameCharacter
that will act as a Context
:
public class GameCharacter {
private CharacterState characterState = new StandingCharacterState(this);
public void setCharacterState(CharacterState characterState) {
this.characterState = characterState;
}
public CharacterState getCharacterState() {
return characterState;
}
public void move() {
characterState.move();
}
public void speak() {
characterState.speak();
}
public void shoot() {
characterState.shoot();
}
public void toNextState() {
characterState.toNextState();
}
}
We can use the above code in the following way:
GameCharacter gameCharacter = new GameCharacter();
System.out.println("Game character state = " + gameCharacter.getCharacterState().getName());
gameCharacter.move();
gameCharacter.speak();
gameCharacter.shoot();
gameCharacter.toNextState();
System.out.println();
System.out.println("Game character state = " + gameCharacter.getCharacterState().getName());
gameCharacter.move();
gameCharacter.speak();
gameCharacter.shoot();
gameCharacter.toNextState();
System.out.println();
System.out.println("Game character state = " + gameCharacter.getCharacterState().getName());
gameCharacter.move();
gameCharacter.speak();
gameCharacter.shoot();
gameCharacter.toNextState();
System.out.println();
System.out.println("Game character state = " + gameCharacter.getCharacterState().getName());
gameCharacter.move();
gameCharacter.speak();
gameCharacter.shoot();
The above code will create the following output:
Game character state = Standing
Standing...
Speaking while standing...
Shooting while standing...
Game character state = Walking
Walking...
Speaking while walking...
Shooting while walking...
Game character state = Running
Running...
Speaking while running...
Shooting while running...
Game character state = Standing
Standing...
Speaking while standing...
Shooting while standing...
Notice how GameCharacter changes its behavior when transiting between states. Each state involves different behavior without increasing the context object’s complexity.
You can find the source code for this example on GitHub.
Limitations
Since the State design pattern requires each state to be encapsulated in a class that implements a common interface, it can easily lead to violating Interface Segregation Principle (ISP) and Liskov Substitution Principle (LSP). If you would observe in your code something like this:
class ConcreteState implements State {
@Override
public void operationA() {
throw new IllegalStateException("this state does not support operationA");
}
@Override
public void operationB() {
// empty method, this state will not support this operation
}
// implementation details
}
Then it might mean that structure offered by this pattern does not match your needs.
Summary
The State design pattern is a behavioral pattern that allows you to implement Finite-State Machine using the Object Oriented Approach. The pattern delegates state-specific behaviors to state objects. It involves three components: Context, State, ConcreteState. The State design pattern is useful when an object has multiple behaviors that need to be switched dynamically based on the object’s state.
References
- Erich G, Richard H, Ralph J, John V. State. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional. October 31, 1994.
- State Design Pattern. Refactoring Guru. Accessed April 10, 2023. https://refactoring.guru/design-patterns/state
- State Design Pattern. Sourcemaking. Accessed April 10, 2023. https://sourcemaking.com/design_patterns/state