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
EasyLet'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
TrafficLightStateclass 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 GreenYellowState— color is"Yellow", action is"Caution", changes to RedGreenState— color is"Green", action is"Go", changes to Yellow
You'll need a forward declaration for
TrafficLightsince the states reference it.- A pure virtual method
TrafficLight.h: Create the context class that maintains the current state.Your
TrafficLightclass should hold astd::unique_ptr<TrafficLightState>and start in the Red state by default. Implement:setState(std::unique_ptr<TrafficLightState> newState)— changes the current statechange()— delegates to the current state's change methoddisplay()— 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
TrafficLightand 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: StopWith input 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopNotice 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRAII as a Pattern3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container