Low-level design3 min
State Pattern
Type: Behavioral Pattern
Relevance: High. Widely used for modeling state machines, UI components, and game development.
The State pattern lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
Real-Life Analogy
Think of a Smartphone. A smartphone behaves differently depending on its state.
- If it is in Unlocked State, pressing the Home button goes to the home screen.
- If it is in Locked State, pressing the Home button goes to the PIN entry screen.
- If it is in Dead Battery State, pressing the Home button does absolutely nothing.
The action (pressHomeButton()) is the same, but the behavior changes completely based on the phone's internal state.
The Problem
When you have an object that behaves differently depending on its state, you usually end up writing massive switch or if/else statements inside every single method (if (state == LOCKED) { ... } else if (state == UNLOCKED) { ... }). This is hard to read, hard to maintain, and violates the Open/Closed Principle.
How to Implement State
Extract all state-specific behavior into separate classes (one class per state) that implement a common State interface. The original object (Context) stores a reference to one of the state objects and delegates all work to it.
Example in Code
// 1. The Context (The Phone)
class Smartphone {
private state: State;
constructor(initialState: State) {
this.state = initialState;
this.state.setContext(this);
}
// The Context allows States to transition it to a new State
public changeState(state: State): void {
console.log(`\nPhone: Transitioning state...`);
this.state = state;
this.state.setContext(this);
}
// Delegates work to the current State
public pressHomeButton(): void {
this.state.pressHomeButton();
}
}
// 2. The State Interface
abstract class State {
protected context: Smartphone = null as any;
public setContext(context: Smartphone) {
this.context = context;
}
public abstract pressHomeButton(): void;
}
// 3. Concrete States
class LockedState extends State {
public pressHomeButton(): void {
console.log("LockedState: Displaying PIN entry screen.");
// Transition to Unlocked after PIN is entered (Simulated)
this.context.changeState(new UnlockedState());
}
}
class UnlockedState extends State {
public pressHomeButton(): void {
console.log("UnlockedState: Going to Home Screen.");
// Transition to Dead Battery (Simulated)
this.context.changeState(new DeadBatteryState());
}
}
class DeadBatteryState extends State {
public pressHomeButton(): void {
console.log("DeadBatteryState: Screen remains black. Nothing happens.");
}
}
// Client Code
const phone = new Smartphone(new LockedState());
phone.pressHomeButton(); // Shows PIN screen, transitions to Unlocked
phone.pressHomeButton(); // Goes to Home Screen, transitions to Dead Battery
phone.pressHomeButton(); // Nothing happens
Class Diagram
State vs Strategy
- Strategy: Strategies are usually completely independent and unaware of each other. The client passes the strategy to the context.
- State: States are aware of each other and trigger transitions from one state to another. The context manages its own state transitions internally.