State Pattern
Explain Like I'm 5...
Imagine a TRAFFIC LIGHT at a busy intersection! The light changes colors, and each color makes the traffic light behave differently.
The Problem:
- 🔴 Red light: Cars must STOP
- 🟡 Yellow light: Cars should SLOW DOWN and prepare to stop
- 🟢 Green light: Cars can GO
- 💡 The traffic light changes its BEHAVIOR based on its current color (state)!
The Solution - State Pattern!
- • Each color is a different STATE
- • Each STATE knows what to do
- • When the light changes color, it changes STATE
- • The traffic light BEHAVES differently in each state automatically!
The Key Idea:
An object can change its BEHAVIOR when its internal state changes! It's like the traffic light appearing to be a completely different object when it turns from red to green - same light, totally different behavior!
Why Is This Pattern Useful?
- ✨No giant if-else or switch statements checking the current state!
- ✨Each state is its own class with its own behavior!
- ✨Easy to add new states without changing existing code!
- ✨The object appears to change its class when state changes!
Pattern Purpose
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. It encapsulates state-specific behavior and delegates requests to the current state object.
When to Use State Pattern
- ✓An object's behavior depends on its state, and it must change behavior at runtime
- ✓Operations have large, multipart conditional statements that depend on the object's state
- ✓State-specific behavior should be defined independently
- ✓You want to avoid duplicate code when several states have similar behavior
Components
Context:
Context: Maintains an instance of a ConcreteState subclass that defines the current state
State:
State: Defines an interface for encapsulating behavior associated with a particular state
ConcreteState:
ConcreteState: Each subclass implements behavior associated with a state of the Context
Java Implementations
Example 1: Vending Machine
A vending machine that changes behavior based on its current state (idle, has money, dispensing).
// State Interfacepublic interface VendingMachineState { void insertMoney(VendingMachine machine); void selectProduct(VendingMachine machine); void dispense(VendingMachine machine); void ejectMoney(VendingMachine machine);}// Concrete State 1: Idle (waiting for money)public class IdleState implements VendingMachineState { @Override public void insertMoney(VendingMachine machine) { System.out.println("Money inserted. Please select a product."); machine.setState(machine.getHasMoneyState()); } @Override public void selectProduct(VendingMachine machine) { System.out.println("Please insert money first!"); } @Override public void dispense(VendingMachine machine) { System.out.println("Please insert money and select product first!"); } @Override public void ejectMoney(VendingMachine machine) { System.out.println("No money to eject!"); }}// Concrete State 2: Has Money (ready to select product)public class HasMoneyState implements VendingMachineState { @Override public void insertMoney(VendingMachine machine) { System.out.println("Money already inserted. Please select a product."); } @Override public void selectProduct(VendingMachine machine) { System.out.println("Product selected. Dispensing..."); machine.setState(machine.getDispensingState()); machine.dispense(); } @Override public void dispense(VendingMachine machine) { System.out.println("Please select a product first!"); } @Override public void ejectMoney(VendingMachine machine) { System.out.println("Money ejected. Returning to idle state."); machine.setState(machine.getIdleState()); }}// Concrete State 3: Dispensing (giving product)public class DispensingState implements VendingMachineState { @Override public void insertMoney(VendingMachine machine) { System.out.println("Please wait, already dispensing product."); } @Override public void selectProduct(VendingMachine machine) { System.out.println("Please wait, already dispensing product."); } @Override public void dispense(VendingMachine machine) { System.out.println("Product dispensed! Enjoy!"); if (machine.getProductCount() > 0) { machine.decrementProductCount(); machine.setState(machine.getIdleState()); } else { System.out.println("Out of stock!"); machine.setState(machine.getIdleState()); } } @Override public void ejectMoney(VendingMachine machine) { System.out.println("Too late! Product is being dispensed."); }}// Context: The Vending Machinepublic class VendingMachine { private VendingMachineState idleState; private VendingMachineState hasMoneyState; private VendingMachineState dispensingState; private VendingMachineState currentState; private int productCount; public VendingMachine(int productCount) { this.productCount = productCount; // Create all possible states idleState = new IdleState(); hasMoneyState = new HasMoneyState(); dispensingState = new DispensingState(); // Start in idle state currentState = idleState; } // Delegate all operations to current state public void insertMoney() { currentState.insertMoney(this); } public void selectProduct() { currentState.selectProduct(this); } public void dispense() { currentState.dispense(this); } public void ejectMoney() { currentState.ejectMoney(this); } // State management public void setState(VendingMachineState state) { this.currentState = state; } // Getters for states public VendingMachineState getIdleState() { return idleState; } public VendingMachineState getHasMoneyState() { return hasMoneyState; } public VendingMachineState getDispensingState() { return dispensingState; } // Product management public int getProductCount() { return productCount; } public void decrementProductCount() { if (productCount > 0) { productCount--; } } public static void main(String[] args) { VendingMachine machine = new VendingMachine(3); System.out.println("=== Scenario 1: Normal Purchase ==="); machine.insertMoney(); machine.selectProduct(); System.out.println("\n=== Scenario 2: Try to select without money ==="); machine.selectProduct(); System.out.println("\n=== Scenario 3: Eject money ==="); machine.insertMoney(); machine.ejectMoney(); System.out.println("\n=== Scenario 4: Double insert ==="); machine.insertMoney(); machine.insertMoney(); machine.selectProduct(); }}Example 2: Phone Call
A phone that behaves differently when ringing, connected, or on hold.
// State Interfacepublic interface PhoneState { void dial(Phone phone, String number); void answer(Phone phone); void hold(Phone phone); void hangUp(Phone phone);}// Concrete State: Phone is Ringingpublic class RingingState implements PhoneState { @Override public void dial(Phone phone, String number) { System.out.println("Already in a call! Cannot dial."); } @Override public void answer(Phone phone) { System.out.println("Call answered! You are now connected."); phone.setState(phone.getConnectedState()); } @Override public void hold(Phone phone) { System.out.println("Cannot hold a ringing call! Answer first."); } @Override public void hangUp(Phone phone) { System.out.println("Call rejected. Back to idle."); phone.setState(phone.getIdleState()); }}// Concrete State: Call is Connectedpublic class ConnectedState implements PhoneState { @Override public void dial(Phone phone, String number) { System.out.println("Already connected to a call!"); } @Override public void answer(Phone phone) { System.out.println("Already connected!"); } @Override public void hold(Phone phone) { System.out.println("Call placed on hold."); phone.setState(phone.getOnHoldState()); } @Override public void hangUp(Phone phone) { System.out.println("Call ended. Back to idle."); phone.setState(phone.getIdleState()); }}// Concrete State: Call is On Holdpublic class OnHoldState implements PhoneState { @Override public void dial(Phone phone, String number) { System.out.println("Cannot dial while call is on hold!"); } @Override public void answer(Phone phone) { System.out.println("Resuming call from hold."); phone.setState(phone.getConnectedState()); } @Override public void hold(Phone phone) { System.out.println("Call is already on hold."); } @Override public void hangUp(Phone phone) { System.out.println("Ending held call. Back to idle."); phone.setState(phone.getIdleState()); }}// Concrete State: Phone is Idlepublic class IdleState implements PhoneState { @Override public void dial(Phone phone, String number) { System.out.println("Dialing " + number + "..."); System.out.println("Phone is ringing..."); phone.setState(phone.getRingingState()); } @Override public void answer(Phone phone) { System.out.println("No incoming call to answer."); } @Override public void hold(Phone phone) { System.out.println("No active call to hold."); } @Override public void hangUp(Phone phone) { System.out.println("No call to hang up."); }}// Context: The Phonepublic class Phone { private PhoneState idleState; private PhoneState ringingState; private PhoneState connectedState; private PhoneState onHoldState; private PhoneState currentState; public Phone() { // Initialize all states idleState = new IdleState(); ringingState = new RingingState(); connectedState = new ConnectedState(); onHoldState = new OnHoldState(); // Start idle currentState = idleState; } // Delegate to current state public void dial(String number) { currentState.dial(this, number); } public void answer() { currentState.answer(this); } public void hold() { currentState.hold(this); } public void hangUp() { currentState.hangUp(this); } // State management public void setState(PhoneState state) { this.currentState = state; } // State getters public PhoneState getIdleState() { return idleState; } public PhoneState getRingingState() { return ringingState; } public PhoneState getConnectedState() { return connectedState; } public PhoneState getOnHoldState() { return onHoldState; } public static void main(String[] args) { Phone phone = new Phone(); System.out.println("=== Making a call ==="); phone.dial("555-1234"); phone.answer(); System.out.println("\n=== Putting call on hold ==="); phone.hold(); System.out.println("\n=== Resuming call ==="); phone.answer(); System.out.println("\n=== Ending call ==="); phone.hangUp(); }}Example 3: Document Workflow
A document that goes through different states: draft, moderation, published.
// State Interfacepublic interface DocumentState { void edit(Document document, String content); void submit(Document document); void approve(Document document); void reject(Document document); void publish(Document document);}// Concrete State: Document in Draftpublic class DraftState implements DocumentState { @Override public void edit(Document document, String content) { System.out.println("Editing draft: " + content); document.setContent(content); } @Override public void submit(Document document) { System.out.println("Submitting draft for moderation..."); document.setState(document.getModerationState()); } @Override public void approve(Document document) { System.out.println("Cannot approve a draft. Submit it first!"); } @Override public void reject(Document document) { System.out.println("Cannot reject a draft."); } @Override public void publish(Document document) { System.out.println("Cannot publish a draft. Submit and approve first!"); }}// Concrete State: Document in Moderationpublic class ModerationState implements DocumentState { @Override public void edit(Document document, String content) { System.out.println("Cannot edit while in moderation!"); } @Override public void submit(Document document) { System.out.println("Document is already in moderation."); } @Override public void approve(Document document) { System.out.println("Document approved! Publishing..."); document.setState(document.getPublishedState()); } @Override public void reject(Document document) { System.out.println("Document rejected. Sending back to draft."); document.setState(document.getDraftState()); } @Override public void publish(Document document) { System.out.println("Document must be approved before publishing!"); }}// Concrete State: Document Publishedpublic class PublishedState implements DocumentState { @Override public void edit(Document document, String content) { System.out.println("Cannot edit a published document directly!"); System.out.println("Create a new draft or unpublish first."); } @Override public void submit(Document document) { System.out.println("Document is already published."); } @Override public void approve(Document document) { System.out.println("Document is already published."); } @Override public void reject(Document document) { System.out.println("Cannot reject a published document."); } @Override public void publish(Document document) { System.out.println("Document is already published!"); }}// Context: The Documentpublic class Document { private DocumentState draftState; private DocumentState moderationState; private DocumentState publishedState; private DocumentState currentState; private String content; private String author; public Document(String author) { this.author = author; this.content = ""; // Initialize all states draftState = new DraftState(); moderationState = new ModerationState(); publishedState = new PublishedState(); // Start in draft state currentState = draftState; } // Delegate to current state public void edit(String content) { currentState.edit(this, content); } public void submit() { currentState.submit(this); } public void approve() { currentState.approve(this); } public void reject() { currentState.reject(this); } public void publish() { currentState.publish(this); } // State management public void setState(DocumentState state) { this.currentState = state; } // State getters public DocumentState getDraftState() { return draftState; } public DocumentState getModerationState() { return moderationState; } public DocumentState getPublishedState() { return publishedState; } // Content management public void setContent(String content) { this.content = content; } public String getContent() { return content; } public String getAuthor() { return author; } public static void main(String[] args) { Document doc = new Document("John Doe"); System.out.println("=== Writing and submitting document ==="); doc.edit("Introduction to State Pattern"); doc.edit("The State Pattern is a behavioral design pattern..."); doc.submit(); System.out.println("\n=== Try to edit during moderation ==="); doc.edit("Cannot edit now!"); System.out.println("\n=== Approve and publish ==="); doc.approve(); System.out.println("\n=== Try to edit published document ==="); doc.edit("Cannot edit published!"); System.out.println("\n=== New document that gets rejected ==="); Document doc2 = new Document("Jane Smith"); doc2.edit("Poor quality content"); doc2.submit(); doc2.reject(); doc2.edit("Improved content after feedback"); doc2.submit(); doc2.approve(); }}Real-World Examples
- 🔌TCP Connection: Different states like Established, Listen, Closed
- 📦Order Processing: Pending, Processing, Shipped, Delivered
- 🎮Game Characters: Different states like Idle, Walking, Running, Jumping
- ▶️Media Player: Playing, Paused, Stopped states
- 🛗Elevator: Moving Up, Moving Down, Stopped, Door Open states
Benefits
- ✅Single Responsibility: State-specific behavior is in separate classes
- ✅Open/Closed Principle: Add new states without changing existing state classes or context
- ✅Simplifies Code: Eliminates bulky state machine conditionals
- ✅State Transitions: State objects can control when state transitions occur
Drawbacks
- ⚠️Can be overkill for simple state machines with few states
- ⚠️Increases number of classes in the system
- ⚠️State classes may need access to context's private data
Key Points to Remember
- 1️⃣State pattern allows objects to alter behavior when internal state changes
- 2️⃣Each state is encapsulated in a separate class
- 3️⃣Context delegates state-specific requests to current state object
- 4️⃣Different from Strategy - State manages its own transitions, Strategy is chosen by client
- 5️⃣States can be shared if they don't have instance variables (Flyweight pattern)
Practice Scenarios
- • Create a traffic light system with Red, Yellow, Green states and automatic transitions
- • Build a music player with Play, Pause, Stop, Fast Forward states
- • Implement an ATM machine with different states (No Card, Has Card, Wrong PIN, Transaction)
- • Design a garage door controller with states (Open, Closed, Opening, Closing, Stopped)
- • Create a booking system with states (Available, Reserved, Booked, Cancelled)