State & Chain of Responsibility in Java
Behavior by state objects; handlers passing requests along.
Switches everywhere
When behavior depends on a mode, the same switch tends to appear in every method. Each new mode means editing all of them, and missing one is a bug. Copying the switch into a helper, or swapping the enum for int constants, doesn't fix the shape.
void press() {
switch (mode) {
case OFF -> mode = ON;
case ON -> mode = BLINK;
case BLINK -> mode = OFF;
}
}
// ...same switch in hold(), render()State: behavior lives in the state
The State pattern gives each state its own class, or enum constant, implementing every behavior. The context delegates to its current state and replaces it on each transition. Each constant here owns its own next(), so the current state decides.
enum Light {
RED { Light next() { return GREEN; } },
GREEN { Light next() { return AMBER; } },
AMBER { Light next() { return RED; } };
abstract Light next();
}Three presses
What does this print?
enum Fan {
OFF { Fan press() { return LOW; } },
LOW { Fan press() { return HIGH; } },
HIGH { Fan press() { return OFF; } };
abstract Fan press();
}
void main() {
IO.println(Fan.LOW.press().press().press());
}LOWHIGHOFF
Show the answer
Round 1: LOW to HIGH. Round 2: HIGH to OFF. Round 3: OFF back to LOW. No switch anywhere: each constant knows its successor, and the current state decides what happens.
Chain of Responsibility
Here, handlers are linked in a line. Each one either handles the request or passes it to the next. The sender only calls the first handler and never knows which one responds. Subclasses handle what they can and call super.handle(r) for the rest.
abstract class Handler {
private Handler next;
Handler then(Handler n) {
next = n;
return n;
}
void handle(Request r) {
if (next != null) next.handle(r);
}
}Who approves $500?
Approvals: a team lead takes expenses under $100, a manager under $1,000, a director anything bigger. Chain: lead, manager, director. Who handles $500, and what does the submitter need to know?
Think about it, then reveal the answer
The lead sees it's over $100 and forwards it; the manager handles it. The submitter just calls the first handler, so adding or reordering approvers when the rules change doesn't affect it.
Not every handler must act
Chain of Responsibility doesn't require every handler to process each request. Usually a handler either handles or forwards. Some chains, like servlet filters, let several handlers act in turn, but that's a choice. The real bug is a handler that does neither, silently dropping the request.
Chains and states at work
Servlet filters and Spring Security's filter chain, logging handlers and HTTP middleware are chains. Order workflows (new, paid, shipped, refunded), media players and game characters are classic State machines.
Key takeaways
- State: one class or enum constant per state
- Transitions replace the current state object
- Chain: each handler handles the request or passes it on
- The sender doesn't know which handler will respond
💡 A vending machine acts differently when empty, idle or paid; a support ticket climbs from bot to agent to manager.
An enum constant with its own body, like RED { ... }, is compiled into its own anonymous subclass of the enum. That's how each constant can carry a different next().
Practice questions
What does this print?
enum Light {
RED { Light next() { return GREEN; } },
GREEN { Light next() { return AMBER; } },
AMBER { Light next() { return RED; } };
abstract Light next();
}
void main() {
IO.println(Light.AMBER.next().next());
}- GREEN
- RED
- AMBER
Check your answer
GREEN. AMBER goes to RED, and RED goes to GREEN. Each constant owns its own transition, so there's no central switch.
Which pattern is this?
abstract class Handler {
private Handler next;
Handler then(Handler n) {
next = n;
return n;
}
void handle(Request r) {
if (next != null) next.handle(r);
}
}- Chain of Responsibility
- State
- Composite
- Observer
Check your answer
Chain of Responsibility. Handlers are linked one after another; each subclass decides whether to handle a request or call super.handle() to pass it along.