State Pattern: Polymorphic Transitions
What This Concept Is
The State pattern lets an object change its behavior when its internal state changes by delegating state-specific behavior to a separate object. The object "appears as if its class changed" when its state changes.
Three roles:
- Context -- the object whose behavior varies. Holds a reference to the current state and exposes the operations callers invoke.
- State interface -- declares the operations whose behavior depends on state.
- Concrete states -- each implements the operations for one state and is responsible for deciding what the next state is.
The key difference from Strategy: in Strategy, the client picks and swaps the strategy. In State, the states themselves drive transitions -- the object's "class" changes as a consequence of what happened inside it.
Why It Matters Here
Lots of real objects have a lifecycle: a document has draft/review/published; an order has placed/paid/shipped/delivered/canceled; a network connection has disconnected/connecting/connected/closing. If that lifecycle is implicit in scattered if statements, every new state or new operation forces edits everywhere.
State pattern localizes "what can happen next and how" inside each state class. Adding a new state is adding a class, not hunting through every method.
Concrete Example
A vending machine with four states; each decides transitions.
interface State {
void insertCoin();
void selectItem();
void dispense();
}
class VendingMachine {
private State current;
int inventory;
State noCoin, hasCoin, sold, soldOut;
VendingMachine(int items) {
inventory = items;
noCoin = new NoCoinState(this);
hasCoin = new HasCoinState(this);
sold = new SoldState(this);
soldOut = new SoldOutState(this);
current = inventory > 0 ? noCoin : soldOut;
}
void setState(State s) { current = s; }
void insertCoin() { current.insertCoin(); }
void selectItem() { current.selectItem(); }
void dispense() { current.dispense(); }
}
class NoCoinState implements State {
private final VendingMachine m;
NoCoinState(VendingMachine m) { this.m = m; }
public void insertCoin() { System.out.println("coin accepted"); m.setState(m.hasCoin); }
public void selectItem() { System.out.println("insert a coin first"); }
public void dispense() { System.out.println("insert a coin first"); }
}
class HasCoinState implements State {
private final VendingMachine m;
HasCoinState(VendingMachine m) { this.m = m; }
public void insertCoin() { System.out.println("coin already inserted"); }
public void selectItem() { m.setState(m.sold); m.dispense(); }
public void dispense() { System.out.println("select an item first"); }
}
class SoldState implements State {
private final VendingMachine m;
SoldState(VendingMachine m) { this.m = m; }
public void insertCoin() { System.out.println("please wait"); }
public void selectItem() { System.out.println("already dispensing"); }
public void dispense() {
m.inventory--;
m.setState(m.inventory > 0 ? m.noCoin : m.soldOut);
}
}
class SoldOutState implements State {
private final VendingMachine m;
SoldOutState(VendingMachine m) { this.m = m; }
public void insertCoin() { System.out.println("sold out"); }
public void selectItem() { System.out.println("sold out"); }
public void dispense() { System.out.println("sold out"); }
}
Each state knows exactly how to respond to every method and which state comes next. Zero switches in VendingMachine.
Common Confusion / Misconception
- State and Strategy look identical on paper. Both delegate behavior to a helper. The difference is who drives transitions. In State, helpers know about each other and the next state. In Strategy, helpers are independent.
- State classes that hold mutable state of their own. Usually states are stateless (or cached) and push data into the context. A state with its own persistent fields becomes a memory surprise when you switch away and back.
- Transition logic duplicated across states. If
CanceledStateandDeliveredStateboth do the same "refund if needed", you want a small helper or a base class, not five repeats. - Creating new state instances on every transition. In a hot loop, this allocates. Cache state instances on the context or in a registry.
How To Use It
- Write down the state names and the transitions between them on paper.
- Declare a state interface that lists every operation whose behavior varies.
- Create one class per state. Each method decides: act, reject, and/or transition.
- On the context, hold
currentStateand expose each operation as a delegation. - Expose
setStateto the state classes (package-private, internal, or via a friend class). - Initialize to the correct starting state explicitly.
Test with a scenario table: "in state X, method Y should end up in state Z with side effect S". One row per cell.
Check Yourself
- Which role knows about the concrete state classes: context, states, or both?
- What distinguishes State from Strategy?
- Why prefer states that do not hold their own mutable data?
- How do you add a new state without modifying all existing ones?
Mini Drill or Application
Model a traffic light with Red, Yellow, Green states and two operations: tick() (time advance) and emergency() (force red).
- Implement each state; each decides the next.
- Handle emergency from any state.
- Write a test scenario that cycles the light three times.
Stop when the light class has no switch on state.