The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
It is the professional way to implement a Finite State Machine (FSM).
The Problem
Imagine you are building a Document Management System. A document can be in different states: Draft, Moderation, or Published.
- If a document is in
Draft, clicking "Publish" moves it toModeration. - If it is in
Moderation, clicking "Publish" moves it toPublished. - If it is already
Published, the "Publish" button should do nothing.
Without the State pattern, your code becomes a massive "if-else" or "switch" statement inside every single method:
public void publish() {
if (state == DRAFT) { ... }
else if (state == MODERATION) { ... }
// This gets messy fast as you add more states!
}
The Solution
We create a separate class for each state. The original object (the Context) stores a reference to a "State" object and delegates all work to it. When the state changes, the Context simply swaps the current state object for a new one.
Step-by-Step Java Implementation
1. The State Interface
Defines the actions that can be performed in any state.
// State.java
public interface State {
void publish(Document doc);
void render();
}
2. Concrete States
Each class handles the logic for that specific state.
// DraftState.java
class DraftState implements State {
public void publish(Document doc) {
System.out.println("Moving document from Draft to Moderation.");
doc.setState(new ModerationState());
}
public void render() { System.out.println("Draft view: Edit tools enabled."); }
}
// PublishedState.java
class PublishedState implements State {
public void publish(Document doc) {
System.out.println("Document is already published. Doing nothing.");
}
public void render() { System.out.println("Public view: Read-only mode."); }
}
3. The Context
The class that maintains the current state.
// Document.java
public class Document {
private State state;
public Document() { this.state = new DraftState(); }
public void setState(State state) { this.state = state; }
public void publish() { state.publish(this); }
public void render() { state.render(); }
}
Full Code for Testing
// Save as StateTest.java
// 1. State Interface
interface State {
void pressPlay(VendingMachine machine);
}
// 2. Concrete States
class WaitingState implements State {
@Override
public void pressPlay(VendingMachine machine) {
System.out.println("[State: Waiting] Coin inserted. Transitioning to Ready...");
machine.setState(new ReadyState());
}
}
class ReadyState implements State {
@Override
public void pressPlay(VendingMachine machine) {
System.out.println("[State: Ready] Product dispensed. Transitioning back to Waiting...");
machine.setState(new WaitingState());
}
}
// 3. Context
class VendingMachine {
private State currentState;
public VendingMachine() {
currentState = new WaitingState(); // Initial state
}
public void setState(State state) {
this.currentState = state;
}
public void performAction() {
currentState.pressPlay(this);
}
}
// 4. Test
public class StateTest {
public static void main(String[] args) {
System.out.println("--- State Pattern Test ---\n");
VendingMachine machine = new VendingMachine();
// Action 1
machine.performAction();
// Action 2
machine.performAction();
System.out.println("\n--- State Cycle Complete ---");
}
}
Why use State?
- Single Responsibility: You move state-specific code into separate classes.
- Open/Closed Principle: You can add new states without changing the existing state classes or the Context.
- Cleaner Code: You eliminate massive, repetitive conditional blocks.
Real-World Example
- TCP Connections: A network socket can be in
Listen,Established, orClosedstates, and its response to a packet depends on its current state. - Vending Machines: The machine reacts differently to a "Select Product" button depending on whether you have inserted a coin or not.