State Pattern in C++ Design Patterns

Explore the State Pattern in C++ to alter behavior when state changes, implement state machines, encapsulate states as objects, and avoid conditional statements.

6.9 State Pattern

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is particularly useful in scenarios where an object must change its behavior based on its state, such as in state machines. By encapsulating states as objects, the State Pattern helps avoid complex conditional statements and enhances code maintainability and scalability.

Intent

The primary intent of the State Pattern is to allow an object to change its behavior when its internal state changes. This pattern encapsulates state-specific behavior within state objects and delegates state-dependent behavior to the current state object.

Key Participants

  1. Context: Maintains an instance of a ConcreteState subclass that defines the current state.
  2. State: Defines an interface for encapsulating the behavior associated with a particular state of the Context.
  3. ConcreteState: Implements behavior associated with a state of the Context.

Applicability

Use the State Pattern when:

  • An object’s behavior depends on its state, and it must change its behavior at runtime depending on that state.
  • Operations have large, multipart conditional statements that depend on the object’s state.

Implementing State Machines

State machines are a common use case for the State Pattern. They consist of states, transitions, and events that trigger transitions. The State Pattern provides a structured way to implement state machines by encapsulating states as objects.

Example: Traffic Light System

Let’s consider a simple traffic light system with three states: Red, Green, and Yellow. Each state has a specific behavior, and the traffic light transitions between these states.

 1#include <iostream>
 2#include <memory>
 3
 4// Forward declaration
 5class TrafficLight;
 6
 7// State interface
 8class State {
 9public:
10    virtual ~State() = default;
11    virtual void handle(TrafficLight& light) = 0;
12};
13
14// Context class
15class TrafficLight {
16private:
17    std::unique_ptr<State> state;
18public:
19    TrafficLight(std::unique_ptr<State> initialState) : state(std::move(initialState)) {}
20    void setState(std::unique_ptr<State> newState) {
21        state = std::move(newState);
22    }
23    void request() {
24        state->handle(*this);
25    }
26};
27
28// Concrete states
29class RedState : public State {
30public:
31    void handle(TrafficLight& light) override;
32};
33
34class GreenState : public State {
35public:
36    void handle(TrafficLight& light) override;
37};
38
39class YellowState : public State {
40public:
41    void handle(TrafficLight& light) override;
42};
43
44// Implementations of state transitions
45void RedState::handle(TrafficLight& light) {
46    std::cout << "Red Light - Stop!" << std::endl;
47    light.setState(std::make_unique<GreenState>());
48}
49
50void GreenState::handle(TrafficLight& light) {
51    std::cout << "Green Light - Go!" << std::endl;
52    light.setState(std::make_unique<YellowState>());
53}
54
55void YellowState::handle(TrafficLight& light) {
56    std::cout << "Yellow Light - Caution!" << std::endl;
57    light.setState(std::make_unique<RedState>());
58}
59
60int main() {
61    TrafficLight light(std::make_unique<RedState>());
62    for (int i = 0; i < 6; ++i) {
63        light.request();
64    }
65    return 0;
66}

Encapsulating States as Objects

In the State Pattern, each state is represented as a separate class that encapsulates the behavior associated with that state. This encapsulation allows for easy addition of new states and modifications to existing states without altering the Context class.

Benefits of Encapsulation

  • Modularity: Each state is a separate class, making it easy to manage and extend.
  • Maintainability: Changes to a state’s behavior do not affect other states or the Context.
  • Scalability: New states can be added with minimal impact on existing code.

Avoiding Conditional Statements

The State Pattern helps eliminate complex conditional statements that are often used to manage state transitions. By delegating state-specific behavior to state objects, the pattern simplifies the logic within the Context class.

Example: Vending Machine

Consider a vending machine with different states: Idle, Selecting, Dispensing, and OutOfStock. Each state has its own behavior, and the machine transitions between these states based on user actions.

 1#include <iostream>
 2#include <memory>
 3
 4// Forward declaration
 5class VendingMachine;
 6
 7// State interface
 8class VendingState {
 9public:
10    virtual ~VendingState() = default;
11    virtual void handle(VendingMachine& machine) = 0;
12};
13
14// Context class
15class VendingMachine {
16private:
17    std::unique_ptr<VendingState> state;
18public:
19    VendingMachine(std::unique_ptr<VendingState> initialState) : state(std::move(initialState)) {}
20    void setState(std::unique_ptr<VendingState> newState) {
21        state = std::move(newState);
22    }
23    void request() {
24        state->handle(*this);
25    }
26};
27
28// Concrete states
29class IdleState : public VendingState {
30public:
31    void handle(VendingMachine& machine) override;
32};
33
34class SelectingState : public VendingState {
35public:
36    void handle(VendingMachine& machine) override;
37};
38
39class DispensingState : public VendingState {
40public:
41    void handle(VendingMachine& machine) override;
42};
43
44class OutOfStockState : public VendingState {
45public:
46    void handle(VendingMachine& machine) override;
47};
48
49// Implementations of state transitions
50void IdleState::handle(VendingMachine& machine) {
51    std::cout << "Machine is idle. Please select an item." << std::endl;
52    machine.setState(std::make_unique<SelectingState>());
53}
54
55void SelectingState::handle(VendingMachine& machine) {
56    std::cout << "Selecting item. Please wait..." << std::endl;
57    machine.setState(std::make_unique<DispensingState>());
58}
59
60void DispensingState::handle(VendingMachine& machine) {
61    std::cout << "Dispensing item. Thank you!" << std::endl;
62    machine.setState(std::make_unique<IdleState>());
63}
64
65void OutOfStockState::handle(VendingMachine& machine) {
66    std::cout << "Out of stock. Please try later." << std::endl;
67}
68
69int main() {
70    VendingMachine machine(std::make_unique<IdleState>());
71    for (int i = 0; i < 3; ++i) {
72        machine.request();
73    }
74    return 0;
75}

Design Considerations

When implementing the State Pattern, consider the following:

  • State Transition Logic: Ensure that transitions between states are well-defined and logical.
  • State Initialization: Initialize the Context with an appropriate initial state.
  • State-Specific Behavior: Encapsulate behavior that varies by state within the state classes.

C++ Specific Features

  • Smart Pointers: Use smart pointers (std::unique_ptr) to manage state objects and ensure proper memory management.
  • Polymorphism: Leverage polymorphism to delegate behavior to state objects.

Differences and Similarities

The State Pattern is often compared to the Strategy Pattern. While both patterns encapsulate behavior, the key difference is that the State Pattern is used when an object’s behavior changes based on its state, whereas the Strategy Pattern is used to select an algorithm at runtime.

Visualizing the State Pattern

Let’s visualize the State Pattern using a state diagram for the traffic light system:

    stateDiagram-v2
	    [*] --> Red
	    Red --> Green: Timer
	    Green --> Yellow: Timer
	    Yellow --> Red: Timer

Diagram Description: The state diagram illustrates the transitions between the Red, Green, and Yellow states of the traffic light system. Each transition is triggered by a timer event.

Try It Yourself

Experiment with the provided code examples by:

  • Adding new states to the traffic light or vending machine systems.
  • Modifying state transitions to introduce new behavior.
  • Implementing a different system using the State Pattern.

Knowledge Check

  • What are the key participants in the State Pattern?
  • How does the State Pattern help avoid complex conditional statements?
  • What is the difference between the State Pattern and the Strategy Pattern?

Embrace the Journey

Remember, mastering design patterns is a journey. As you continue to explore and apply the State Pattern, you’ll gain a deeper understanding of how to manage state-dependent behavior in your applications. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026