Design Patterns State
The State pattern lets an object change its behavior when its internal condition changes. From outside, it appears to switch classes at runtime. Long chains of if-else checks disappear, and each condition earns its own small class.
The Real-World Analogy
A traffic light acts differently in each color. Red stops vehicles, green releases them, and yellow warns drivers. The light itself stays one device, and its current color decides what happens next.
Problem Without State
class Order {
status = "NEW"
ship() {
if status == "NEW" → error: pay first
else if status == "PAID" → send package, status = "SHIPPED"
else if status == "SHIPPED" → error: already shipped
else if status == "CANCELLED" → error: order closed
}
cancel() { ...another long chain... }
pay() { ...another long chain... }
}
Every new status forces edits to every method. The class grows tangled and fragile.
Solution With State
┌──────────────┐ delegates ┌──────────────────┐
│ Order │ ───────────▶ │ «interface» │
│ - state │ │ OrderState │
│ + ship() │ │ + pay() │
│ + cancel() │ │ + ship() │
└──────────────┘ │ + cancel() │
└────────▲─────────┘
┌──────────────────┼───────────────────┐
┌─────┴─────┐ ┌──────┴─────┐ ┌──────┴──────┐
│ NewState │ │ PaidState │ │ShippedState │
└───────────┘ └────────────┘ └─────────────┘
The Order forwards each request to its current state object. Each state class holds the rules for one condition only.
State Transition Diagram
pay() ship()
┌─────┐ ─────────▶ ┌──────┐ ─────────▶ ┌─────────┐
│ NEW │ │ PAID │ │ SHIPPED │
└──┬──┘ └──┬───┘ └─────────┘
│ cancel() │ cancel()
▼ ▼
┌───────────────────────────┐
│ CANCELLED │
└───────────────────────────┘
Arrows show the legal moves. Any move missing from the diagram becomes an error message inside the matching state class.
Code Example
interface OrderState {
void pay(Order order);
void ship(Order order);
}
class NewState implements OrderState {
public void pay(Order order) { order.setState(new PaidState()); print("Payment received"); }
public void ship(Order order) { print("Pay before shipping"); }
}
class PaidState implements OrderState {
public void pay(Order order) { print("Already paid"); }
public void ship(Order order) { order.setState(new ShippedState()); print("Package sent"); }
}
class ShippedState implements OrderState {
public void pay(Order order) { print("Already paid"); }
public void ship(Order order) { print("Already shipped"); }
}
class Order {
private OrderState state = new NewState();
void setState(OrderState s) { state = s; }
void pay() { state.pay(this); }
void ship() { state.ship(this); }
}
Order o = new Order();
o.ship(); // Pay before shipping
o.pay(); // Payment received
o.ship(); // Package sentWho Controls Transitions
State classes often decide the next state themselves, as the example shows. A central table inside the context class offers another option. Small machines work well with the first style, and larger machines gain clarity from the second.
Real Projects Using State
| Scenario | States |
|---|---|
| Media player | Stopped, playing, paused |
| Document workflow | Draft, review, published |
| Vending machine | Idle, has money, dispensing |
| Network connection | Closed, connecting, open |
State Versus Strategy
Both patterns hold a swappable object. Strategy gets chosen by the client and stays put. State changes by itself as events happen, and states often know about each other.
Advantages and Disadvantages
| Advantages | Disadvantages |
|---|---|
| Removes giant condition chains | More classes for small machines |
| New states join without edits to old ones | Transition logic spreads across files |
| Each state stays easy to test | Overkill for two or three states |
Step-by-Step Walkthrough
Trace the three lines at the end of the code example. A new Order starts with a NewState object. The first ship() call reaches NewState.ship(), which prints a reminder to pay. A pay() call reaches NewState.pay(), which swaps the order's state to PaidState. Another ship() call lands in PaidState.ship(), which moves the order to ShippedState.
The Order class never checks a status flag. Each state object knows its own rules.
Designing a State Machine
- List every state before writing code.
- Draw the arrows for legal moves and label each with its trigger.
- Mark final states that accept no further moves.
- Decide what every state does when it receives an unexpected action.
Key Takeaways
- State moves condition-specific behavior into separate classes.
- The context object delegates every call to its current state.
- A transition diagram clarifies the legal moves before coding starts.
- Strategy stays fixed after selection, while State changes on its own.
