Skip to content

State Design Pattern (Behavioral)

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.


The State pattern is closely related to the concept of a Finite State Machine. At any given moment, there’s a finite number of states which a program can be in. Within any unique state, the program behaves differently, and the program can be switched from one state to another instantaneously.

However, these transitions are dependent on the current state. If you try to implement this using massive conditionals (if or switch statements), the code becomes extremely difficult to maintain. Any change to the transition logic requires changing conditionals in every method of the class.

The State pattern suggests that you create new classes for all possible states of an object and extract all state-specific behaviors into these classes.

Instead of implementing all behaviors on its own, the original object, called context, stores a reference to one of the state objects that represents its current state, and delegates all the state-related work to that object. To transition the context into another state, you replace the active state object with another object that represents that new state.

The buttons and switches on your smartphone behave differently depending on the current state of the device:

  • When the phone is unlocked, pressing buttons leads to various actions.
  • When the phone is locked, pressing any button leads to the unlock screen.
  • When the phone’s charge is low, pressing buttons might show a warning.

  1. Context: Stores a reference to one of the concrete state objects and delegates to it all state-specific work. The context communicates with the state object via the state interface.
  2. State Interface: Declares the state-specific methods. These methods should make sense for all concrete states.
  3. Concrete States: Provide their own implementations for the state-specific methods. To avoid duplication of similar code in multiple states, you may provide intermediate abstract classes that encapsulate common behavior.
  4. Transitioning: Both Context and Concrete States can set the next state of the context and perform the actual state transition by replacing the state object linked to the context.
classDiagram
    class Context {
        -state: State
        +Context(initialState)
        +changeState(state)
        +request()
    }
    class State {
        <<interface>>
        +handle(context)
    }
    class ConcreteStateA {
        +handle(context)
    }
    class ConcreteStateB {
        +handle(context)
    }

    Context o-- State
    State <|-- ConcreteStateA
    State <|-- ConcreteStateB

# The base state class declares methods that all concrete
# states should implement.
abstract class State:
protected field player: AudioPlayer
constructor State(player):
this.player = player
abstract method clickLock()
abstract method clickPlay()
abstract method clickNext()
# Concrete states implement behaviors associated with a state.
class PlayingState extends State:
method clickLock():
player.changeState(new LockedState(player))
method clickPlay():
player.stopPlayback()
player.changeState(new ReadyState(player))
method clickNext():
player.nextSong()
# The Context class has a reference to the current state.
class AudioPlayer:
field state: State
field UI, volume, playlist, currentSong
constructor AudioPlayer():
this.state = new ReadyState(this)
method changeState(state):
this.state = state
method clickLock():
state.clickLock()

  • Dynamic Behavior: Use the State pattern when you have an object that behaves differently depending on its current state, the number of states is enormous, and the state-specific code changes frequently.
  • Massive Conditionals: Use the pattern when you have a class polluted with massive conditionals that alter how the class behaves according to the current values of the class’s fields.
  • Code Duplication: Use State when you have a lot of duplicate code across similar states and transitions of a condition-based state machine.
  1. Declare the state interface.
  2. For every actual state, create a class that implements the state interface.
  3. In the context class, add a reference field of the state interface type and a public setter that allows overriding the value of that field.
  4. Replace the state conditionals in the context with calls to corresponding methods of the state object.
  5. To change the state of the context, create an instance of one of the state classes and pass it to the context. You can do this from the context itself, or from concrete states.

Pros:

  • Single Responsibility Principle: Organize the code related to particular states into separate classes.
  • Open/Closed Principle: Introduce new states without changing existing state classes or the context.
  • Simplify the code of the context by eliminating bulky state machine conditionals.

Cons:

  • Applying the pattern can be overkill if a state machine has only a few states or rarely changes.
  • Bridge, Strategy, and State (and to some degree Adapter) have very similar structures based on composition. However, they all solve different problems.
  • Strategy makes these objects completely independent and unaware of each other. State doesn’t restrict dependencies between concrete states, letting them alter the state of the context at will.

// State
public interface IState
{
void Handle(Context context);
}
// Concrete States
public class StateA : IState
{
public void Handle(Context context)
{
Console.WriteLine("State A β†’ switch to B");
context.SetState(new StateB());
}
}
public class StateB : IState
{
public void Handle(Context context)
{
Console.WriteLine("State B β†’ switch to A");
context.SetState(new StateA());
}
}
// Context
public class Context
{
private IState _state;
public Context(IState state)
{
_state = state;
}
public void SetState(IState state)
{
_state = state;
}
public void Request()
{
_state.Handle(this);
}
}
// Usage
class Program
{
static void Main()
{
var context = new Context(new StateA());
context.Request();
context.Request();
context.Request();
}
}