State Pattern – Let Objects Evolve Their Behavior Dynamically

Learn how the State Pattern helps objects change behavior dynamically based on internal state. Includes real-world examples, UML, and C#/Python code.

State Pattern: Let Objects Evolve Their Behavior Dynamically

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. It appears as if the object changed its class. This pattern is particularly useful when an object must change its behavior at runtime depending on its current state.

What is the State Pattern?

The State Pattern encapsulates varying behavior for the same object based on its internal state, and delegates state-specific behavior to different state classes. It helps in organizing code that has lots of conditional logic based on the object's state.

Real-World Analogy

Think of a vending machine. Depending on its state—waiting for selection, dispensing item, or out of service—it behaves differently. The machine's operations are delegated to different internal state objects.

Participants

  • Context: Maintains an instance of a ConcreteState subclass that defines the current state.
  • State: Defines the interface for encapsulating behavior associated with a particular state.
  • Concrete States: Each subclass implements behavior associated with a state of the context.

UML Diagram

classDiagram
    class Context {
        - state: State
        + Request()
        + ChangeState(State)
    }

    class State {
        <<interface>>
        + Handle(Context)
    }

    class ConcreteStateA {
        + Handle(Context)
    }

    class ConcreteStateB {
        + Handle(Context)
    }

    Context --> State
    State <|.. ConcreteStateA
    State <|.. ConcreteStateB

Implementation

// State Interface
public interface IState {
    void Handle(Context context);
}

// Concrete State A
public class ConcreteStateA : IState {
    public void Handle(Context context) {
        Console.WriteLine("State A: Switching to State B");
        context.ChangeState(new ConcreteStateB());
    }
}

// Concrete State B
public class ConcreteStateB : IState {
    public void Handle(Context context) {
        Console.WriteLine("State B: Switching to State A");
        context.ChangeState(new ConcreteStateA());
    }
}

// Context
public class Context {
    private IState _state;

    public Context(IState state) {
        _state = state;
    }

    public void ChangeState(IState state) {
        _state = state;
    }

    public void Request() {
        _state.Handle(this);
    }
}
from abc import ABC, abstractmethod

class State(ABC):
    @abstractmethod
    def handle(self, context):
        pass

class ConcreteStateA(State):
    def handle(self, context):
        print("State A: Switching to State B")
        context.change_state(ConcreteStateB())

class ConcreteStateB(State):
    def handle(self, context):
        print("State B: Switching to State A")
        context.change_state(ConcreteStateA())

class Context:
    def __init__(self, state):
        self._state = state

    def change_state(self, state):
        self._state = state

    def request(self):
        self._state.handle(self)

Use Cases

  • Media players: Play, Pause, Stop states
  • Network connections: Connected, Disconnected, Connecting
  • UI Workflows or Page Navigation

More Examples (C#)


public interface IPlayerState
{
    void Play(MediaPlayer player);
    void Pause(MediaPlayer player);
    void Stop(MediaPlayer player);
}

public class PlayingState : IPlayerState
{
    public void Play(MediaPlayer player) => Console.WriteLine("Already playing.");
    public void Pause(MediaPlayer player)
    {
        Console.WriteLine("Pausing playback.");
        player.ChangeState(new PausedState());
    }
    public void Stop(MediaPlayer player)
    {
        Console.WriteLine("Stopping playback.");
        player.ChangeState(new StoppedState());
    }
}

public class PausedState : IPlayerState
{
    public void Play(MediaPlayer player)
    {
        Console.WriteLine("Resuming playback.");
        player.ChangeState(new PlayingState());
    }
    public void Pause(MediaPlayer player) => Console.WriteLine("Already paused.");
    public void Stop(MediaPlayer player)
    {
        Console.WriteLine("Stopping from pause.");
        player.ChangeState(new StoppedState());
    }
}

public class StoppedState : IPlayerState
{
    public void Play(MediaPlayer player)
    {
        Console.WriteLine("Starting playback.");
        player.ChangeState(new PlayingState());
    }
    public void Pause(MediaPlayer player) => Console.WriteLine("Can't pause. Not playing.");
    public void Stop(MediaPlayer player) => Console.WriteLine("Already stopped.");
}

public class MediaPlayer
{
    private IPlayerState _state;
    public MediaPlayer() => _state = new StoppedState();
    public void ChangeState(IPlayerState state) => _state = state;
    public void Play() => _state.Play(this);
    public void Pause() => _state.Pause(this);
    public void Stop() => _state.Stop(this);
}

// Usage
// var player = new MediaPlayer();
// player.Play();  // Starting playback.
// player.Pause(); // Pausing playback.
// player.Stop();  // Stopping from pause.
            
        

public interface IOrderState
{
    void Next(Order order);
    void Cancel(Order order);
}

public class NewOrderState : IOrderState
{
    public void Next(Order order)
    {
        Console.WriteLine("Order paid.");
        order.ChangeState(new PaidOrderState());
    }
    public void Cancel(Order order)
    {
        Console.WriteLine("Order cancelled.");
        order.ChangeState(new CancelledOrderState());
    }
}

public class PaidOrderState : IOrderState
{
    public void Next(Order order)
    {
        Console.WriteLine("Order shipped.");
        order.ChangeState(new ShippedOrderState());
    }
    public void Cancel(Order order)
    {
        Console.WriteLine("Cannot cancel, already paid.");
    }
}

public class ShippedOrderState : IOrderState
{
    public void Next(Order order)
    {
        Console.WriteLine("Order delivered.");
        order.ChangeState(new DeliveredOrderState());
    }
    public void Cancel(Order order)
    {
        Console.WriteLine("Cannot cancel, already shipped.");
    }
}

public class DeliveredOrderState : IOrderState
{
    public void Next(Order order) => Console.WriteLine("Order already delivered.");
    public void Cancel(Order order) => Console.WriteLine("Cannot cancel, already delivered.");
}

public class CancelledOrderState : IOrderState
{
    public void Next(Order order) => Console.WriteLine("Order is cancelled.");
    public void Cancel(Order order) => Console.WriteLine("Order is already cancelled.");
}

public class Order
{
    private IOrderState _state;
    public Order() => _state = new NewOrderState();
    public void ChangeState(IOrderState state) => _state = state;
    public void Next() => _state.Next(this);
    public void Cancel() => _state.Cancel(this);
}

// Usage
// var order = new Order();
// order.Next();   // Order paid.
// order.Next();   // Order shipped.
// order.Cancel(); // Cannot cancel, already shipped.
            
        

Benefits

  • Organizes code related to particular states in separate classes: The State Pattern encourages you to create a separate class for each possible state of an object. This means all the logic and behavior for a specific state is grouped together, making the code easier to read and maintain. For example, if you have a Document that can be in "Draft", "Moderation", or "Published" states, each state would have its own class
  • Makes state transitions explicit and easier to manage Instead of using lots of if or switch statements to check the current state and decide what to do, the State Pattern lets the object change its state by switching to a different state class. This makes transitions clear and centralizes the logic for moving from one state to another, reducing bugs and confusion.
  • Improves maintainability and scalability: By separating state-specific logic, it becomes much easier to add new states or modify existing ones without affecting unrelated code. This modular approach means you can scale your application or make changes with less risk of introducing errors elsewhere.

Drawbacks

  • Can lead to a large number of classes: Each state is typically implemented as a separate class. For objects with many possible states, this can result in a proliferation of classes, making the codebase harder to navigate and increasing maintenance overhead.
  • Increased complexity if states are trivial: If the behavior differences between states are minimal, using the State Pattern may introduce unnecessary abstraction and complexity. In such cases, simple conditional logic might be more appropriate and easier to understand.
  • State transitions can be harder to trace: Since state transitions are encapsulated within state classes, it may be less obvious how and when transitions occur, especially in large systems. This can make debugging and understanding the flow of state changes more challenging.
  • Potential for tight coupling between Context and State classes: The Context class must be aware of all possible state classes to switch between them, which can lead to tight coupling and reduce flexibility if not managed carefully.

Conclusion

The State Pattern is an excellent way to manage complex state-dependent behavior. By encapsulating state-specific logic into individual classes, your system becomes easier to maintain, extend, and test. It brings clarity to how state transitions occur and allows objects to behave differently based on their internal state.

Test Your Knowledge