The state design pattern is one of the patterns from the Gang of Four design patterns. It is a behavioral design pattern. The primary intent of the state pattern is to allow an object to alter its behavior when its internal state is changed.
In your day-to-day implementation, you may be encountering the state pattern a lot, because every object has a state. And there are situations where based on the state of the object, its behavior changes.
The use case for the State Pattern
Let us consider the scenario where we are dealing with cars. A car can have multiple states. And based on the state of the car it can behave differently.
For the simplicity of the use case, we are going to focus only on three states for the car. And these states are:
- Started state
- Running state
- And stopped state
And we can change the state of the car based on the following actions.
- For starting a car, we can use the start action.
- Similarly, for stopping a car we can use the stop action.
- And finally, for running a car we can use the acceleration action.
Based on each of these actions, the state of the car will change. And based on the state, the behavior of the car will change.
For example, when the state of the car is stopped, you cannot accelerate the car. Hence the running behavior is not possible when the car is in the stopped state. Only when the state of the car is started, that is when we can perform the acceleration action, and the car can achieve the running behavior.
C#/.NET 6 code for the State Pattern
For the purpose of the demonstration, I will create a .NET 6/C# console application using Visual Studio 2022. Once the project is ready, I will create a new class to represent the Car.
The enumerations
The Car class will have an enum as State, which will represent all the states of the car. Which will be Stopped, Started, and Running.
Similarly, we will have another enum inside the Car class for the Action. The Action enum will have three represent three actions that we can perform on the car. And these actions are Stop, Start and Accelerate.
namespace StatePattern.Demo;
internal class Car
{
public enum State
{
Stopped,
Started,
Running
}
public enum Action
{
Stop,
Start,
Accelerate
}
}
The state of the Car class
The Car class will have one property. The name of the property is CurrentState. And the CurrentState property will expose the current state of the car.
The CurrentState property is a Get-only property. And it will just return the private variable state, which is nothing but the internal state of the car. And the default value of the variable state will be Stopped.
namespace StatePattern.Demo;
internal class Car
{
public enum State
{
Stopped,
Started,
Running
}
public enum Action
{
Stop,
Start,
Accelerate
}
private State state = State.Stopped;
public State CurrentState { get { return state; } }
}
Method to change the state of the car
Next, what we want to do is to allow a way to change the state of the car. And the change in state is going to change the behavior of the car.
There are multiple ways of doing it, but the most common way of doing it is through an observer pattern. That is basically through an event subscription, and based on the event you can change the state.
For simplicity in this example of the state machine, I’m going to create a function that is going to take care of changing the state of the car. And the function is going to be a public function, that will return void take Action as its parameter. The name of the function will be TakeAction.
The state machine logic
The main job of this function is to change the state of the Car based on the Action parameter. The logic of this function will be as follows:
- If the current state is Stopped and the Action parameter is Start in that case the state will be Started.
- But if the current state is Started the Action parameter is Accelerate, then the state will be Running.
- And if the state is Started and the Action parameter is Stop, in that case, the state will be Stopped.
- Similarly, if the state is Running and the action is Stopped the state will be Stopped.
- But for all other conditions, the state will remain unchanged.
The above logic I described is nothing but a state machine. So based on an action we are changing the state of the object. And depending on the state, the object will behave differently.
For example, if the state of the car is Stopped, it can be only started. We cannot change it to accelerate and we cannot stop it, as it is already stopped.
Similarly, when it is started it cannot be started once again because it’s already started. It can only be stopped or accelerated.
And if it is running then the only thing we can do is stop the car. We cannot start it because it’s already started.
namespace StatePattern.Demo;
internal class Car
{
public enum State
{
Stopped,
Started,
Running
}
public enum Action
{
Stop,
Start,
Accelerate
}
private State state = State.Stopped;
public State CurrentState { get { return state; } }
public void TakeAction(Action action)
{
state = (state, action) switch
{
(State.Stopped, Action.Start) => State.Started,
(State.Started, Action.Accelerate) => State.Running,
(State.Started, Action.Stop) => State.Stopped,
(State.Running, Action.Stop) => State.Stopped,
_ => state
};
}
}
Running the code
To test this, we will update the Program class. And inside the Program class, we will create a new instance of the Car. And after we create the instance, we will call the TakeAction method of Car with multiple Actions, and print the state of the car every time.
using StatePattern.Demo;
var car = new Car();
Console.WriteLine($"State: {car.CurrentState}");
car.TakeAction(Car.Action.Start);
Console.WriteLine($"State: {car.CurrentState}");
car.TakeAction(Car.Action.Start);
Console.WriteLine($"State: {car.CurrentState}");
car.TakeAction(Car.Action.Accelerate);
Console.WriteLine($"State: {car.CurrentState}");
car.TakeAction(Car.Action.Stop);
Console.WriteLine($"State: {car.CurrentState}");
When we run this program, we will see that even though we called the TakeAction twice with the Start action, its state changed only once.
In the output, initially, the car was stopped, when we send the action Start it got started. Next, we send the action Start again, the state did not change because the car was already started.
Then we send the action Accelerate which changes it to running. And then we send the action Stop which changes it to stop the car.
Conclusion
This is exactly what we expected out of the state machine. This example shows how to implement the state design pattern using a state machine. And this is a very simple state machine using a switch condition.
A Youtube video for this implementation is available here.