Behavioral 4 — State
In one line
An object changes behavior when its state changes — state logic moves from if/else soup into per-state classes. The vending machine pattern.
Key ideas
- 01
Solves: objects with a few explicit states and transitions (vending, ATM, elevators, order lifecycle).
- 02
Context delegates to a State object; each State decides the next State.
- 03
State vs Strategy: same shape; Strategy is caller-selected, State transitions itself.
- 04
Illegal transition handling lives inside each state (e.g. dispense while empty).
- 05
Real-life example: a traffic light. Pressing the pedestrian button does different things when it's green, yellow, or red. The light's behaviour depends on its current state.
- 06
Interview tip: draw the state diagram (circles = states, arrows = events) BEFORE writing code. It shows every allowed and forbidden move at a glance.
Java / Spring map
- →
Spring StateMachine; Long running workflows (order status) are State at enterprise scale.
Code & diagrams
Vending machine core — the flagship State pattern example.
public class VendingMachine {
private State state = new IdleState();
private int balance = 0;
public void setState(State s) { this.state = s; }
public int balance() { return balance; }
public void addMoney(int c) { balance += c; state.onMoney(this, c); }
public void select(String item) { state.onSelect(this, item); }
public void dispense() { state.onDispense(this); }
public void doDispense(String item) {
System.out.println("dispense: " + item + " (balance " + balance + ")");
balance = 0;
}
}
public interface State {
void onMoney(VendingMachine m, int cents);
void onSelect(VendingMachine m, String item);
void onDispense(VendingMachine m);
}
public class IdleState implements State {
public void onMoney(VendingMachine m, int c) { m.setState(new HasMoneyState()); }
public void onSelect(VendingMachine m, String i) { throw new IllegalStateException("insert coins first"); }
public void onDispense(VendingMachine m) { throw new IllegalStateException("nothing selected"); }
}
public class HasMoneyState implements State {
public void onMoney(VendingMachine m, int c) { /* accumulate more */ }
public void onSelect(VendingMachine m, String i) {
if (!inventoryHas(m, i)) throw new IllegalStateException("sold out");
if (m.balance() >= priceOf(i)) m.setState(new DispenseState(i));
else throw new IllegalStateException("insufficient funds");
}
public void onDispense(VendingMachine m) { throw new IllegalStateException("select first"); }
}
public class DispenseState implements State {
private final String item;
public DispenseState(String item) { this.item = item; }
public void onMoney(VendingMachine m, int c) { throw new IllegalStateException("wait"); }
public void onSelect(VendingMachine m, String i) { throw new IllegalStateException("dispensing"); }
public void onDispense(VendingMachine m) { m.doDispense(item); m.setState(new IdleState()); }
}Explain without notes
What makes each state class an OCP win over a big switch on state?
Practice
Add a SellOutState (item empty → refund promo) and wire its transitions.
Trade-offs
- ↔
States multiply fast; if transitions are simple, an enum with a transition table is often enough.
Completion checklist
I can draw the state diagram first, then code one class per state with illegal moves rejected.