Menu
Coddy logo textTech

State Pattern

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 100 of 104.

The State pattern allows an object to change its behavior when its internal state changes, making it appear as if the object changed its class. Instead of using complex conditional statements to handle different states, you encapsulate each state as a separate class.

The pattern consists of a Context that maintains a reference to the current state, a State interface defining state-specific behavior, and Concrete States that implement behavior for each state:

#include <iostream>
#include <memory>

class Document;  // Forward declaration

// State interface
class DocumentState {
public:
    virtual void publish(Document& doc) = 0;
    virtual std::string getName() const = 0;
    virtual ~DocumentState() = default;
};

// Context
class Document {
    std::unique_ptr<DocumentState> state;
public:
    Document();
    void setState(std::unique_ptr<DocumentState> newState) {
        state = std::move(newState);
    }
    void publish() { state->publish(*this); }
    std::string getStateName() const { return state->getName(); }
};

// Concrete States
class Draft : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Draft"; }
};

class Review : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Review"; }
};

class Published : public DocumentState {
public:
    void publish(Document& doc) override {
        std::cout << "Already published\n";
    }
    std::string getName() const override { return "Published"; }
};

void Draft::publish(Document& doc) {
    std::cout << "Moving to review\n";
    doc.setState(std::make_unique<Review>());
}

void Review::publish(Document& doc) {
    std::cout << "Publishing document\n";
    doc.setState(std::make_unique<Published>());
}

Document::Document() : state(std::make_unique<Draft>()) {}

Each state handles the publish() action differently and is responsible for transitioning to the next state. The Document doesn't need to know the transition logic - it simply delegates to the current state. This eliminates large switch statements and makes adding new states straightforward.

Use State when an object's behavior depends heavily on its state and you have many conditional statements that switch on the object's state.

challenge icon

Challenge

Easy

Let's build a Traffic Light Controller using the State pattern. You'll create a system where a traffic light cycles through different states (Red, Yellow, Green), with each state determining what happens when the light changes and what message is displayed. This is a classic example of the State pattern—the traffic light's behavior depends entirely on its current state.

You'll organize your code across three files:

  • TrafficLightState.h: Define the state interface and all concrete state classes.

    Create an abstract TrafficLightState class with:

    • A pure virtual method change(TrafficLight& light) that handles transitioning to the next state
    • A pure virtual method getColor() that returns the current color as a string
    • A pure virtual method getAction() that returns what drivers should do (e.g., "Stop", "Caution", "Go")
    • A virtual destructor

    Implement three concrete states:

    • RedState — color is "Red", action is "Stop", changes to Green
    • YellowState — color is "Yellow", action is "Caution", changes to Red
    • GreenState — color is "Green", action is "Go", changes to Yellow

    You'll need a forward declaration for TrafficLight since the states reference it.

  • TrafficLight.h: Create the context class that maintains the current state.

    Your TrafficLight class should hold a std::unique_ptr<TrafficLightState> and start in the Red state by default. Implement:

    • setState(std::unique_ptr<TrafficLightState> newState) — changes the current state
    • change() — delegates to the current state's change method
    • display() — prints the light's status in the format: [Color]: [Action]
  • main.cpp: Demonstrate the traffic light cycling through states.

    Read one input: the number of state changes to perform (integer).

    Create a TrafficLight and display its initial state. Then perform the specified number of changes, displaying the state after each change.

For example, with input 3:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop

With input 6:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

Notice how the traffic light cycles through its states in a predictable pattern: Red → Green → Yellow → Red. Each state knows which state comes next and handles its own transition. The TrafficLight class doesn't need any conditional logic to determine what happens—it simply delegates to whatever state it's currently in.

Cheat sheet

The State pattern allows an object to change its behavior when its internal state changes. Instead of using complex conditional statements, each state is encapsulated as a separate class.

The pattern consists of three components:

  • State interface: Defines state-specific behavior
  • Concrete States: Implement behavior for each state
  • Context: Maintains a reference to the current state

Basic structure:

#include <memory>

class Context;  // Forward declaration

// State interface
class State {
public:
    virtual void handleAction(Context& ctx) = 0;
    virtual std::string getName() const = 0;
    virtual ~State() = default;
};

// Context
class Context {
    std::unique_ptr<State> state;
public:
    void setState(std::unique_ptr<State> newState) {
        state = std::move(newState);
    }
    void performAction() { 
        state->handleAction(*this); 
    }
    std::string getStateName() const { 
        return state->getName(); 
    }
};

// Concrete State
class ConcreteState : public State {
public:
    void handleAction(Context& ctx) override {
        // Handle action and transition to next state
        ctx.setState(std::make_unique<AnotherState>());
    }
    std::string getName() const override { 
        return "ConcreteState"; 
    }
};

Each state handles actions differently and is responsible for transitioning to the next state. The Context delegates behavior to the current state without needing to know transition logic.

Use State when an object's behavior depends heavily on its state and you want to avoid large conditional statements.

Try it yourself

#include <iostream>
#include "TrafficLight.h"

using namespace std;

int main() {
    // Read the number of state changes
    int numChanges;
    cin >> numChanges;
    
    // TODO: Create a TrafficLight object
    
    // TODO: Display the initial state
    
    // TODO: Perform the specified number of changes
    // After each change, display the current state
    
    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming