State Pattern
State Pattern: Behavior That Changes with State
Section titled “State Pattern: Behavior That Changes with State”Now let’s dive into the State Pattern - one of the most elegant behavioral design patterns that allows an object to alter its behavior when its internal state changes. The object will appear to change its class!
Why State Pattern?
Section titled “Why State Pattern?”Imagine a vending machine. When it has no money inserted, pressing the dispense button does nothing. When money is inserted, pressing dispense gives you a product. When it’s out of stock, it refunds your money. The machine behaves differently based on its state. The State Pattern works the same way!
The State Pattern allows an object to change its behavior when its internal state changes. Instead of having giant if/else or switch statements checking the state, each state becomes its own class that knows how to handle requests for that state.
What’s the Use of State Pattern?
Section titled “What’s the Use of State Pattern?”The State Pattern is useful when:
- Object behavior depends on state - Different actions based on internal state
- You have complex state-dependent logic - Many if/else checking state
- State transitions are well-defined - Clear rules for state changes
- You want to add new states easily - Without modifying existing code
- States should be explicit - Each state is a first-class object
What Happens If We Don’t Use State Pattern?
Section titled “What Happens If We Don’t Use State Pattern?”Without the State Pattern, you might:
- Giant switch/if-else statements - Checking state everywhere
- Scattered state logic - Same checks in multiple methods
- Hard to add states - Modify many places for new state
- Violation of OCP - Need to modify existing code
- Difficult to understand - State behavior spread across methods
Simple Example: The Document Workflow
Section titled “Simple Example: The Document Workflow”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”State Transitions
Section titled “State Transitions”Here’s how the document transitions between states:
stateDiagram-v2
[*] --> Draft: Create Document
Draft --> Draft: edit()
Draft --> Moderation: publish()
Moderation --> Published: approve()
Moderation --> Draft: reject()
Published --> Draft: edit()
Published --> [*]: archive()
note right of Draft: Can edit freely
note right of Moderation: Under review
note right of Published: Live content
Interaction Flow
Section titled “Interaction Flow”sequenceDiagram
participant Client
participant Document
participant DraftState
participant ModerationState
participant PublishedState
Client->>Document: new Document()
Document->>DraftState: set initial state
Client->>Document: edit("New content")
activate Document
Document->>DraftState: edit(document)
DraftState->>DraftState: Update content
DraftState-->>Document: Content updated
deactivate Document
Client->>Document: publish()
activate Document
Document->>DraftState: publish(document)
DraftState->>Document: set_state(ModerationState)
DraftState-->>Document: Sent to moderation
deactivate Document
Note over Client,PublishedState: State changed! Same method, different behavior
Client->>Document: publish()
activate Document
Document->>ModerationState: publish(document)
ModerationState->>Document: set_state(PublishedState)
ModerationState-->>Document: Published!
deactivate Document
The Problem
Section titled “The Problem”You’re building a document management system where documents go through different states (Draft, Moderation, Published). Without State Pattern:
# ❌ Without State Pattern - Giant if/else everywhere!
class Document: def __init__(self, content: str): self.content = content self.state = "draft" # draft, moderation, published
def edit(self, new_content: str): # Problem: Check state in every method! if self.state == "draft": self.content = new_content print(f"📝 Content updated: {new_content}") elif self.state == "moderation": print("❌ Cannot edit - document is under moderation") elif self.state == "published": # Move back to draft for editing self.content = new_content self.state = "draft" print(f"📝 Content updated, moved back to draft") # What if we add a new state? Modify here!
def publish(self): # Problem: Same state checks repeated! if self.state == "draft": self.state = "moderation" print("📤 Document sent to moderation") elif self.state == "moderation": self.state = "published" print("✅ Document published!") elif self.state == "published": print("ℹ️ Document is already published") # What if we add a new state? Modify here too!
def reject(self): # Problem: And here too! if self.state == "draft": print("❌ Cannot reject - document is not in moderation") elif self.state == "moderation": self.state = "draft" print("🔙 Document rejected, back to draft") elif self.state == "published": print("❌ Cannot reject - document is already published") # Every new method needs all state checks!
# Problems:# - State checks in every method# - Adding new state = modify ALL methods# - Hard to understand state behavior# - Violates Open/Closed Principle// ❌ Without State Pattern - Giant if/else everywhere!
public class Document { private String content; private String state = "draft"; // draft, moderation, published
public Document(String content) { this.content = content; }
public void edit(String newContent) { // Problem: Check state in every method! if (state.equals("draft")) { this.content = newContent; System.out.println("📝 Content updated: " + newContent); } else if (state.equals("moderation")) { System.out.println("❌ Cannot edit - document is under moderation"); } else if (state.equals("published")) { // Move back to draft for editing this.content = newContent; this.state = "draft"; System.out.println("📝 Content updated, moved back to draft"); } // What if we add a new state? Modify here! }
public void publish() { // Problem: Same state checks repeated! if (state.equals("draft")) { this.state = "moderation"; System.out.println("📤 Document sent to moderation"); } else if (state.equals("moderation")) { this.state = "published"; System.out.println("✅ Document published!"); } else if (state.equals("published")) { System.out.println("ℹ️ Document is already published"); } // What if we add a new state? Modify here too! }
public void reject() { // Problem: And here too! if (state.equals("draft")) { System.out.println("❌ Cannot reject - document is not in moderation"); } else if (state.equals("moderation")) { this.state = "draft"; System.out.println("🔙 Document rejected, back to draft"); } else if (state.equals("published")) { System.out.println("❌ Cannot reject - document is already published"); } // Every new method needs all state checks! }}
// Problems:// - State checks in every method// - Adding new state = modify ALL methods// - Hard to understand state behavior// - Violates Open/Closed PrincipleProblems:
- State checks scattered across every method
- Adding a new state requires modifying ALL methods
- Hard to understand what each state does
- Violates Open/Closed Principle
The Solution: State Pattern
Section titled “The Solution: State Pattern”Class Structure
Section titled “Class Structure”classDiagram
class DocumentState {
<<interface>>
+edit(doc, content) void
+publish(doc) void
+reject(doc) void
}
class DraftState {
+edit(doc, content) void
+publish(doc) void
+reject(doc) void
}
class ModerationState {
+edit(doc, content) void
+publish(doc) void
+reject(doc) void
}
class PublishedState {
+edit(doc, content) void
+publish(doc) void
+reject(doc) void
}
class Document {
-state: DocumentState
-content: str
+set_state(state) void
+edit(content) void
+publish() void
+reject() void
}
DocumentState <|.. DraftState : implements
DocumentState <|.. ModerationState : implements
DocumentState <|.. PublishedState : implements
Document --> DocumentState : delegates to
note for DocumentState "Each state knows how\nto handle requests"
note for Document "Context delegates\nto current state"
from abc import ABC, abstractmethod
# Step 1: Define the State interfaceclass DocumentState(ABC): """State interface - defines behavior for each state"""
@abstractmethod def edit(self, document: 'Document', content: str) -> None: """Edit the document""" pass
@abstractmethod def publish(self, document: 'Document') -> None: """Publish the document""" pass
@abstractmethod def reject(self, document: 'Document') -> None: """Reject the document""" pass
@property @abstractmethod def name(self) -> str: """Get state name""" pass
# Step 2: Implement Concrete Statesclass DraftState(DocumentState): """Draft state - document can be freely edited"""
@property def name(self) -> str: return "Draft"
def edit(self, document: 'Document', content: str) -> None: document._content = content print(f"📝 Content updated: '{content}'")
def publish(self, document: 'Document') -> None: document.set_state(ModerationState()) print("📤 Document sent to moderation")
def reject(self, document: 'Document') -> None: print("❌ Cannot reject - document is not in moderation")
class ModerationState(DocumentState): """Moderation state - document is under review"""
@property def name(self) -> str: return "Moderation"
def edit(self, document: 'Document', content: str) -> None: print("❌ Cannot edit - document is under moderation")
def publish(self, document: 'Document') -> None: document.set_state(PublishedState()) print("✅ Document approved and published!")
def reject(self, document: 'Document') -> None: document.set_state(DraftState()) print("🔙 Document rejected, back to draft")
class PublishedState(DocumentState): """Published state - document is live"""
@property def name(self) -> str: return "Published"
def edit(self, document: 'Document', content: str) -> None: document._content = content document.set_state(DraftState()) print(f"📝 Content updated: '{content}' - moved back to draft")
def publish(self, document: 'Document') -> None: print("ℹ️ Document is already published")
def reject(self, document: 'Document') -> None: print("❌ Cannot reject - document is already published")
# Step 3: Create the Context classclass Document: """Context - the document whose behavior changes based on state"""
def __init__(self, content: str): self._content = content self._state: DocumentState = DraftState() # Initial state
def set_state(self, state: DocumentState) -> None: """Change the document state""" old_state = self._state.name self._state = state print(f" [State: {old_state} → {state.name}]")
def edit(self, content: str) -> None: """Edit the document - behavior depends on state""" print(f"\n📋 Attempting to edit in {self._state.name} state...") self._state.edit(self, content)
def publish(self) -> None: """Publish the document - behavior depends on state""" print(f"\n📋 Attempting to publish in {self._state.name} state...") self._state.publish(self)
def reject(self) -> None: """Reject the document - behavior depends on state""" print(f"\n📋 Attempting to reject in {self._state.name} state...") self._state.reject(self)
@property def content(self) -> str: return self._content
@property def state_name(self) -> str: return self._state.name
# Step 4: Use the patterndef main(): print("=" * 50) print("Document Workflow (State Pattern)") print("=" * 50)
# Create a new document (starts in Draft state) doc = Document("Initial content") print(f"📄 Created document in {doc.state_name} state")
# Edit in draft state - works! doc.edit("Updated content in draft")
# Publish - moves to moderation doc.publish()
# Try to edit in moderation - doesn't work! doc.edit("Trying to edit in moderation")
# Approve (publish again) - moves to published doc.publish()
# Try to publish again - already published doc.publish()
# Edit published document - moves back to draft doc.edit("Making changes to published doc")
# Publish again to go through the workflow doc.publish() # Draft -> Moderation doc.reject() # Moderation -> Draft
print("\n" + "=" * 50) print("✅ State Pattern: Behavior changes with state!") print("✅ No if/else chains - each state handles itself!")
if __name__ == "__main__": main()// Step 1: Define the State interfaceinterface DocumentState { /** * State interface - defines behavior for each state */ void edit(Document document, String content); void publish(Document document); void reject(Document document); String getName();}
// Step 2: Implement Concrete Statesclass DraftState implements DocumentState { /** * Draft state - document can be freely edited */ @Override public String getName() { return "Draft"; }
@Override public void edit(Document document, String content) { document.setContent(content); System.out.println("📝 Content updated: '" + content + "'"); }
@Override public void publish(Document document) { document.setState(new ModerationState()); System.out.println("📤 Document sent to moderation"); }
@Override public void reject(Document document) { System.out.println("❌ Cannot reject - document is not in moderation"); }}
class ModerationState implements DocumentState { /** * Moderation state - document is under review */ @Override public String getName() { return "Moderation"; }
@Override public void edit(Document document, String content) { System.out.println("❌ Cannot edit - document is under moderation"); }
@Override public void publish(Document document) { document.setState(new PublishedState()); System.out.println("✅ Document approved and published!"); }
@Override public void reject(Document document) { document.setState(new DraftState()); System.out.println("🔙 Document rejected, back to draft"); }}
class PublishedState implements DocumentState { /** * Published state - document is live */ @Override public String getName() { return "Published"; }
@Override public void edit(Document document, String content) { document.setContent(content); document.setState(new DraftState()); System.out.println("📝 Content updated: '" + content + "' - moved back to draft"); }
@Override public void publish(Document document) { System.out.println("ℹ️ Document is already published"); }
@Override public void reject(Document document) { System.out.println("❌ Cannot reject - document is already published"); }}
// Step 3: Create the Context classclass Document { /** * Context - the document whose behavior changes based on state */ private String content; private DocumentState state;
public Document(String content) { this.content = content; this.state = new DraftState(); // Initial state }
public void setState(DocumentState state) { // Change the document state String oldState = this.state.getName(); this.state = state; System.out.println(" [State: " + oldState + " → " + state.getName() + "]"); }
public void edit(String content) { // Edit the document - behavior depends on state System.out.println("\n📋 Attempting to edit in " + state.getName() + " state..."); state.edit(this, content); }
public void publish() { // Publish the document - behavior depends on state System.out.println("\n📋 Attempting to publish in " + state.getName() + " state..."); state.publish(this); }
public void reject() { // Reject the document - behavior depends on state System.out.println("\n📋 Attempting to reject in " + state.getName() + " state..."); state.reject(this); }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getStateName() { return state.getName(); }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(50)); System.out.println("Document Workflow (State Pattern)"); System.out.println("=".repeat(50));
// Create a new document (starts in Draft state) Document doc = new Document("Initial content"); System.out.println("📄 Created document in " + doc.getStateName() + " state");
// Edit in draft state - works! doc.edit("Updated content in draft");
// Publish - moves to moderation doc.publish();
// Try to edit in moderation - doesn't work! doc.edit("Trying to edit in moderation");
// Approve (publish again) - moves to published doc.publish();
// Try to publish again - already published doc.publish();
// Edit published document - moves back to draft doc.edit("Making changes to published doc");
// Publish again to go through the workflow doc.publish(); // Draft -> Moderation doc.reject(); // Moderation -> Draft
System.out.println("\n" + "=".repeat(50)); System.out.println("✅ State Pattern: Behavior changes with state!"); System.out.println("✅ No if/else chains - each state handles itself!"); }}Real-World Software Example: Order Processing System
Section titled “Real-World Software Example: Order Processing System”Now let’s see a realistic software example - an e-commerce order processing system with multiple states.
The Problem
Section titled “The Problem”You’re building an order system where orders go through states: Pending → Paid → Shipped → Delivered (or Cancelled at various points). Without State Pattern:
# ❌ Without State Pattern - State checks everywhere!
class Order: def __init__(self, order_id: str, items: list, total: float): self.order_id = order_id self.items = items self.total = total self.state = "pending" # pending, paid, shipped, delivered, cancelled
def pay(self, amount: float): if self.state == "pending": if amount >= self.total: self.state = "paid" print(f"✅ Order {self.order_id} paid!") else: print(f"❌ Insufficient payment: ${amount} < ${self.total}") elif self.state == "paid": print("ℹ️ Order already paid") elif self.state == "shipped": print("ℹ️ Order already shipped") elif self.state == "delivered": print("ℹ️ Order already delivered") elif self.state == "cancelled": print("❌ Cannot pay - order is cancelled")
def ship(self, tracking_number: str): if self.state == "pending": print("❌ Cannot ship - order not paid") elif self.state == "paid": self.tracking_number = tracking_number self.state = "shipped" print(f"📦 Order {self.order_id} shipped! Tracking: {tracking_number}") elif self.state == "shipped": print("ℹ️ Order already shipped") elif self.state == "delivered": print("ℹ️ Order already delivered") elif self.state == "cancelled": print("❌ Cannot ship - order is cancelled")
def deliver(self): if self.state == "pending": print("❌ Cannot deliver - order not paid") elif self.state == "paid": print("❌ Cannot deliver - order not shipped") elif self.state == "shipped": self.state = "delivered" print(f"✅ Order {self.order_id} delivered!") elif self.state == "delivered": print("ℹ️ Order already delivered") elif self.state == "cancelled": print("❌ Cannot deliver - order is cancelled")
def cancel(self): if self.state == "pending": self.state = "cancelled" print(f"❌ Order {self.order_id} cancelled") elif self.state == "paid": self.state = "cancelled" print(f"❌ Order {self.order_id} cancelled - refund initiated") elif self.state == "shipped": print("❌ Cannot cancel - order already shipped") elif self.state == "delivered": print("❌ Cannot cancel - order already delivered") elif self.state == "cancelled": print("ℹ️ Order already cancelled")
# Problems:# - 5 states × 4 methods = 20 if branches!# - Adding new state = modify ALL methods# - Adding new method = add ALL state checks// ❌ Without State Pattern - State checks everywhere!
public class Order { private String orderId; private List<String> items; private double total; private String state = "pending"; // pending, paid, shipped, delivered, cancelled private String trackingNumber;
public void pay(double amount) { if (state.equals("pending")) { if (amount >= total) { state = "paid"; System.out.println("✅ Order " + orderId + " paid!"); } else { System.out.println("❌ Insufficient payment: $" + amount + " < $" + total); } } else if (state.equals("paid")) { System.out.println("ℹ️ Order already paid"); } else if (state.equals("shipped")) { System.out.println("ℹ️ Order already shipped"); } else if (state.equals("delivered")) { System.out.println("ℹ️ Order already delivered"); } else if (state.equals("cancelled")) { System.out.println("❌ Cannot pay - order is cancelled"); } }
public void ship(String trackingNumber) { if (state.equals("pending")) { System.out.println("❌ Cannot ship - order not paid"); } else if (state.equals("paid")) { this.trackingNumber = trackingNumber; state = "shipped"; System.out.println("📦 Order " + orderId + " shipped! Tracking: " + trackingNumber); } else if (state.equals("shipped")) { System.out.println("ℹ️ Order already shipped"); } else if (state.equals("delivered")) { System.out.println("ℹ️ Order already delivered"); } else if (state.equals("cancelled")) { System.out.println("❌ Cannot ship - order is cancelled"); } }
// ... more methods with same pattern
// Problems: // - 5 states × 4 methods = 20 if branches! // - Adding new state = modify ALL methods // - Adding new method = add ALL state checks}Problems:
- 5 states × 4 methods = 20 if-else branches!
- Adding a new state requires modifying ALL methods
- State behavior scattered across methods
- Very error-prone and hard to maintain
The Solution: State Pattern
Section titled “The Solution: State Pattern”State Diagram
Section titled “State Diagram”stateDiagram-v2
[*] --> Pending: Create Order
Pending --> Paid: pay()
Pending --> Cancelled: cancel()
Paid --> Shipped: ship()
Paid --> Cancelled: cancel() + refund
Shipped --> Delivered: deliver()
Delivered --> [*]: Complete
Cancelled --> [*]: Complete
note right of Pending: Awaiting payment
note right of Paid: Ready to ship
note right of Shipped: In transit
note right of Delivered: Complete!
note right of Cancelled: Order cancelled
from abc import ABC, abstractmethodfrom typing import Optionalfrom dataclasses import dataclass, fieldfrom datetime import datetime
# Step 1: Define the State interfaceclass OrderState(ABC): """State interface for order states"""
@abstractmethod def pay(self, order: 'Order', amount: float) -> None: pass
@abstractmethod def ship(self, order: 'Order', tracking_number: str) -> None: pass
@abstractmethod def deliver(self, order: 'Order') -> None: pass
@abstractmethod def cancel(self, order: 'Order') -> None: pass
@property @abstractmethod def name(self) -> str: pass
# Step 2: Implement Concrete Statesclass PendingState(OrderState): """Pending state - awaiting payment"""
@property def name(self) -> str: return "Pending"
def pay(self, order: 'Order', amount: float) -> None: if amount >= order.total: order.amount_paid = amount order.set_state(PaidState()) print(f"✅ Payment of ${amount:.2f} received!") else: print(f"❌ Insufficient payment: ${amount:.2f} < ${order.total:.2f}")
def ship(self, order: 'Order', tracking_number: str) -> None: print("❌ Cannot ship - order not paid yet")
def deliver(self, order: 'Order') -> None: print("❌ Cannot deliver - order not paid yet")
def cancel(self, order: 'Order') -> None: order.set_state(CancelledState()) print("🚫 Order cancelled")
class PaidState(OrderState): """Paid state - ready to ship"""
@property def name(self) -> str: return "Paid"
def pay(self, order: 'Order', amount: float) -> None: print("ℹ️ Order already paid")
def ship(self, order: 'Order', tracking_number: str) -> None: order.tracking_number = tracking_number order.shipped_at = datetime.now() order.set_state(ShippedState()) print(f"📦 Order shipped! Tracking: {tracking_number}")
def deliver(self, order: 'Order') -> None: print("❌ Cannot deliver - order not shipped yet")
def cancel(self, order: 'Order') -> None: order.set_state(CancelledState()) print(f"🚫 Order cancelled - refund of ${order.amount_paid:.2f} initiated")
class ShippedState(OrderState): """Shipped state - in transit"""
@property def name(self) -> str: return "Shipped"
def pay(self, order: 'Order', amount: float) -> None: print("ℹ️ Order already paid")
def ship(self, order: 'Order', tracking_number: str) -> None: print("ℹ️ Order already shipped")
def deliver(self, order: 'Order') -> None: order.delivered_at = datetime.now() order.set_state(DeliveredState()) print("✅ Order delivered successfully!")
def cancel(self, order: 'Order') -> None: print("❌ Cannot cancel - order already shipped. Please initiate return after delivery.")
class DeliveredState(OrderState): """Delivered state - order complete"""
@property def name(self) -> str: return "Delivered"
def pay(self, order: 'Order', amount: float) -> None: print("ℹ️ Order already paid and delivered")
def ship(self, order: 'Order', tracking_number: str) -> None: print("ℹ️ Order already delivered")
def deliver(self, order: 'Order') -> None: print("ℹ️ Order already delivered")
def cancel(self, order: 'Order') -> None: print("❌ Cannot cancel - order already delivered. Please initiate return.")
class CancelledState(OrderState): """Cancelled state - order terminated"""
@property def name(self) -> str: return "Cancelled"
def pay(self, order: 'Order', amount: float) -> None: print("❌ Cannot pay - order is cancelled")
def ship(self, order: 'Order', tracking_number: str) -> None: print("❌ Cannot ship - order is cancelled")
def deliver(self, order: 'Order') -> None: print("❌ Cannot deliver - order is cancelled")
def cancel(self, order: 'Order') -> None: print("ℹ️ Order already cancelled")
# Step 3: Create the Context class@dataclassclass OrderItem: """Item in an order""" name: str price: float quantity: int
class Order: """Context - the order whose behavior changes based on state"""
def __init__(self, order_id: str, items: list[OrderItem]): self.order_id = order_id self.items = items self.total = sum(item.price * item.quantity for item in items) self.amount_paid: float = 0 self.tracking_number: Optional[str] = None self.shipped_at: Optional[datetime] = None self.delivered_at: Optional[datetime] = None self._state: OrderState = PendingState() self.created_at = datetime.now()
def set_state(self, state: OrderState) -> None: """Change order state""" old_state = self._state.name self._state = state print(f" [Order {self.order_id}: {old_state} → {state.name}]")
def pay(self, amount: float) -> None: """Pay for the order""" print(f"\n💳 Processing payment of ${amount:.2f}...") self._state.pay(self, amount)
def ship(self, tracking_number: str) -> None: """Ship the order""" print(f"\n📦 Processing shipment...") self._state.ship(self, tracking_number)
def deliver(self) -> None: """Mark order as delivered""" print(f"\n🚚 Processing delivery...") self._state.deliver(self)
def cancel(self) -> None: """Cancel the order""" print(f"\n🚫 Processing cancellation...") self._state.cancel(self)
def get_status(self) -> str: """Get order status summary""" return f"""Order: {self.order_id}Status: {self._state.name}Items: {len(self.items)}Total: ${self.total:.2f}Paid: ${self.amount_paid:.2f}Tracking: {self.tracking_number or 'N/A'}"""
# Step 4: Use the patterndef main(): print("=" * 60) print("E-Commerce Order Processing (State Pattern)") print("=" * 60)
# Create an order items = [ OrderItem("Laptop", 999.99, 1), OrderItem("Mouse", 29.99, 2), ] order = Order("ORD-001", items) print(f"📝 Created order {order.order_id}") print(f" Total: ${order.total:.2f}")
# Try to ship before paying - should fail order.ship("TRACK123")
# Pay for the order order.pay(500) # Insufficient order.pay(1059.97) # Exact amount
# Try to pay again - already paid order.pay(100)
# Ship the order order.ship("TRACK-12345-XYZ")
# Try to cancel after shipping - should fail order.cancel()
# Deliver the order order.deliver()
# Print final status print("\n" + "=" * 60) print("Final Order Status:") print(order.get_status())
# Demonstrate cancellation flow print("=" * 60) print("Testing Cancellation Flow:") print("=" * 60)
order2 = Order("ORD-002", [OrderItem("Keyboard", 79.99, 1)]) print(f"📝 Created order {order2.order_id}")
order2.pay(79.99) order2.cancel() # Should work - refund initiated
print("\n✅ State Pattern: Clean state-based behavior!") print("✅ Adding new state = Just one new class!")
if __name__ == "__main__": main()import java.time.LocalDateTime;import java.util.*;
// Step 1: Define the State interfaceinterface OrderState { /** * State interface for order states */ void pay(Order order, double amount); void ship(Order order, String trackingNumber); void deliver(Order order); void cancel(Order order); String getName();}
// Step 2: Implement Concrete Statesclass PendingState implements OrderState { /** * Pending state - awaiting payment */ @Override public String getName() { return "Pending"; }
@Override public void pay(Order order, double amount) { if (amount >= order.getTotal()) { order.setAmountPaid(amount); order.setState(new PaidState()); System.out.printf("✅ Payment of $%.2f received!%n", amount); } else { System.out.printf("❌ Insufficient payment: $%.2f < $%.2f%n", amount, order.getTotal()); } }
@Override public void ship(Order order, String trackingNumber) { System.out.println("❌ Cannot ship - order not paid yet"); }
@Override public void deliver(Order order) { System.out.println("❌ Cannot deliver - order not paid yet"); }
@Override public void cancel(Order order) { order.setState(new CancelledState()); System.out.println("🚫 Order cancelled"); }}
class PaidState implements OrderState { /** * Paid state - ready to ship */ @Override public String getName() { return "Paid"; }
@Override public void pay(Order order, double amount) { System.out.println("ℹ️ Order already paid"); }
@Override public void ship(Order order, String trackingNumber) { order.setTrackingNumber(trackingNumber); order.setShippedAt(LocalDateTime.now()); order.setState(new ShippedState()); System.out.println("📦 Order shipped! Tracking: " + trackingNumber); }
@Override public void deliver(Order order) { System.out.println("❌ Cannot deliver - order not shipped yet"); }
@Override public void cancel(Order order) { order.setState(new CancelledState()); System.out.printf("🚫 Order cancelled - refund of $%.2f initiated%n", order.getAmountPaid()); }}
class ShippedState implements OrderState { /** * Shipped state - in transit */ @Override public String getName() { return "Shipped"; }
@Override public void pay(Order order, double amount) { System.out.println("ℹ️ Order already paid"); }
@Override public void ship(Order order, String trackingNumber) { System.out.println("ℹ️ Order already shipped"); }
@Override public void deliver(Order order) { order.setDeliveredAt(LocalDateTime.now()); order.setState(new DeliveredState()); System.out.println("✅ Order delivered successfully!"); }
@Override public void cancel(Order order) { System.out.println("❌ Cannot cancel - order already shipped. Please initiate return after delivery."); }}
class DeliveredState implements OrderState { /** * Delivered state - order complete */ @Override public String getName() { return "Delivered"; }
@Override public void pay(Order order, double amount) { System.out.println("ℹ️ Order already paid and delivered"); }
@Override public void ship(Order order, String trackingNumber) { System.out.println("ℹ️ Order already delivered"); }
@Override public void deliver(Order order) { System.out.println("ℹ️ Order already delivered"); }
@Override public void cancel(Order order) { System.out.println("❌ Cannot cancel - order already delivered. Please initiate return."); }}
class CancelledState implements OrderState { /** * Cancelled state - order terminated */ @Override public String getName() { return "Cancelled"; }
@Override public void pay(Order order, double amount) { System.out.println("❌ Cannot pay - order is cancelled"); }
@Override public void ship(Order order, String trackingNumber) { System.out.println("❌ Cannot ship - order is cancelled"); }
@Override public void deliver(Order order) { System.out.println("❌ Cannot deliver - order is cancelled"); }
@Override public void cancel(Order order) { System.out.println("ℹ️ Order already cancelled"); }}
// Order Item classclass OrderItem { private String name; private double price; private int quantity;
public OrderItem(String name, double price, int quantity) { this.name = name; this.price = price; this.quantity = quantity; }
public double getSubtotal() { return price * quantity; }}
// Step 3: Create the Context classclass Order { /** * Context - the order whose behavior changes based on state */ private String orderId; private List<OrderItem> items; private double total; private double amountPaid; private String trackingNumber; private LocalDateTime shippedAt; private LocalDateTime deliveredAt; private OrderState state;
public Order(String orderId, List<OrderItem> items) { this.orderId = orderId; this.items = items; this.total = items.stream().mapToDouble(OrderItem::getSubtotal).sum(); this.amountPaid = 0; this.state = new PendingState(); }
public void setState(OrderState state) { String oldState = this.state.getName(); this.state = state; System.out.println(" [Order " + orderId + ": " + oldState + " → " + state.getName() + "]"); }
public void pay(double amount) { System.out.printf("%n💳 Processing payment of $%.2f...%n", amount); state.pay(this, amount); }
public void ship(String trackingNumber) { System.out.println("\n📦 Processing shipment..."); state.ship(this, trackingNumber); }
public void deliver() { System.out.println("\n🚚 Processing delivery..."); state.deliver(this); }
public void cancel() { System.out.println("\n🚫 Processing cancellation..."); state.cancel(this); }
// Getters and setters public double getTotal() { return total; } public double getAmountPaid() { return amountPaid; } public void setAmountPaid(double amount) { this.amountPaid = amount; } public void setTrackingNumber(String tracking) { this.trackingNumber = tracking; } public void setShippedAt(LocalDateTime time) { this.shippedAt = time; } public void setDeliveredAt(LocalDateTime time) { this.deliveredAt = time; } public String getOrderId() { return orderId; } public String getStateName() { return state.getName(); }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(60)); System.out.println("E-Commerce Order Processing (State Pattern)"); System.out.println("=".repeat(60));
// Create an order List<OrderItem> items = Arrays.asList( new OrderItem("Laptop", 999.99, 1), new OrderItem("Mouse", 29.99, 2) ); Order order = new Order("ORD-001", items); System.out.println("📝 Created order " + order.getOrderId()); System.out.printf(" Total: $%.2f%n", order.getTotal());
// Try to ship before paying - should fail order.ship("TRACK123");
// Pay for the order order.pay(500); // Insufficient order.pay(1059.97); // Exact amount
// Try to pay again - already paid order.pay(100);
// Ship the order order.ship("TRACK-12345-XYZ");
// Try to cancel after shipping - should fail order.cancel();
// Deliver the order order.deliver();
System.out.println("\n✅ State Pattern: Clean state-based behavior!"); System.out.println("✅ Adding new state = Just one new class!"); }}State Pattern Variants
Section titled “State Pattern Variants”There are different ways to implement the State Pattern:
1. States as Singletons
Section titled “1. States as Singletons”When states are stateless, they can be singletons:
# Singleton States - when states are statelessclass DraftState: _instance = None
def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance
def publish(self, document): document.set_state(ModerationState())
# Usage - always same instancestate1 = DraftState()state2 = DraftState()assert state1 is state2 # True - same instance// Singleton States - when states are statelessclass DraftState implements DocumentState { private static final DraftState INSTANCE = new DraftState();
private DraftState() {}
public static DraftState getInstance() { return INSTANCE; }
@Override public void publish(Document document) { document.setState(ModerationState.getInstance()); }}
// Usage - always same instanceDocumentState state1 = DraftState.getInstance();DocumentState state2 = DraftState.getInstance();assert state1 == state2; // True - same instancePros: Memory efficient, no unnecessary object creation
Cons: States can’t hold instance-specific data
2. State with History
Section titled “2. State with History”Track state history for auditing:
# State with history trackingclass Context: def __init__(self): self._state = InitialState() self._state_history = []
def set_state(self, state): # Record state transition self._state_history.append({ 'from': self._state.name, 'to': state.name, 'timestamp': datetime.now() }) self._state = state
def get_history(self): return self._state_history// State with history trackingclass Context { private State state; private List<StateTransition> stateHistory = new ArrayList<>();
public void setState(State state) { // Record state transition stateHistory.add(new StateTransition( this.state.getName(), state.getName(), LocalDateTime.now() )); this.state = state; }
public List<StateTransition> getHistory() { return stateHistory; }}3. Hierarchical States
Section titled “3. Hierarchical States”States can have sub-states:
# Hierarchical states - states with sub-statesclass ShippedState(OrderState): """Parent state with sub-states"""
def __init__(self, sub_state: str = "in_transit"): self.sub_state = sub_state # in_transit, out_for_delivery
def update_location(self, order, location): if location == "local_hub": self.sub_state = "out_for_delivery" print(f"📍 Package at local hub - out for delivery!")// Hierarchical states - states with sub-statesclass ShippedState implements OrderState { /** * Parent state with sub-states */ private String subState = "in_transit"; // in_transit, out_for_delivery
public void updateLocation(Order order, String location) { if (location.equals("local_hub")) { this.subState = "out_for_delivery"; System.out.println("📍 Package at local hub - out for delivery!"); } }}When to Use State Pattern?
Section titled “When to Use State Pattern?”Use State Pattern when:
✅ Object behavior depends on state - Different actions for different states
✅ You have many state-checking conditionals - if/else chains everywhere
✅ State transitions are well-defined - Clear rules for state changes
✅ You want to add states easily - Without modifying existing code
✅ States should be explicit - First-class state objects
When NOT to Use State Pattern?
Section titled “When NOT to Use State Pattern?”Don’t use State Pattern when:
❌ Few states with simple transitions - Direct if/else might be clearer
❌ State doesn’t affect behavior - Just data, not behavior
❌ States are not well-defined - Unclear transitions
❌ Over-engineering - Don’t add complexity for 2-3 simple states
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: State Objects Holding Context Reference Permanently
Section titled “Mistake 1: State Objects Holding Context Reference Permanently”# ❌ Bad: State holds permanent context referenceclass BadState: def __init__(self, context): self.context = context # Bad: Permanent reference
def do_something(self): self.context.transition()
# ✅ Good: Context passed as parameterclass GoodState: def do_something(self, context): # Good: Passed as parameter context.transition()// ❌ Bad: State holds permanent context referenceclass BadState implements State { private Context context; // Bad: Permanent reference
public BadState(Context context) { this.context = context; }
public void doSomething() { context.transition(); }}
// ✅ Good: Context passed as parameterclass GoodState implements State { public void doSomething(Context context) { // Good: Passed as parameter context.transition(); }}Mistake 2: Context Knowing About All Concrete States
Section titled “Mistake 2: Context Knowing About All Concrete States”# ❌ Bad: Context creates specific statesclass BadContext: def handle(self): if some_condition: self.state = DraftState() # Bad: Context knows concrete states else: self.state = PublishedState()
# ✅ Good: States handle their own transitionsclass GoodContext: def handle(self): self.state.handle(self) # Good: State decides next state// ❌ Bad: Context creates specific statesclass BadContext { public void handle() { if (someCondition) { this.state = new DraftState(); // Bad: Context knows concrete states } else { this.state = new PublishedState(); } }}
// ✅ Good: States handle their own transitionsclass GoodContext { public void handle() { state.handle(this); // Good: State decides next state }}Mistake 3: Missing Default Behavior in States
Section titled “Mistake 3: Missing Default Behavior in States”# ❌ Bad: No default behavior - need to implement everythingclass BadState(ABC): @abstractmethod def method1(self, ctx): pass @abstractmethod def method2(self, ctx): pass @abstractmethod def method3(self, ctx): pass # Every state must implement ALL methods!
# ✅ Good: Base state with default behaviorclass BaseState(ABC): def method1(self, ctx): print("Operation not available in this state")
def method2(self, ctx): print("Operation not available in this state")
# States only override what they need// ❌ Bad: No default behavior - need to implement everythinginterface BadState { void method1(Context ctx); void method2(Context ctx); void method3(Context ctx); // Every state must implement ALL methods!}
// ✅ Good: Abstract base state with default behaviorabstract class BaseState implements State { public void method1(Context ctx) { System.out.println("Operation not available in this state"); }
public void method2(Context ctx) { System.out.println("Operation not available in this state"); }
// States only override what they need}Benefits of State Pattern
Section titled “Benefits of State Pattern”- No if/else chains - Each state handles its own behavior
- Easy to add states - Just create a new state class
- Single Responsibility - Each state has one job
- Open/Closed Principle - Add states without modifying existing code
- Explicit state machine - State diagram maps to code
- Testable - Each state can be tested independently
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is State Pattern?
Section titled “What is State Pattern?”State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object appears to change its class.
Why Use It?
Section titled “Why Use It?”- ✅ Eliminate if/else chains - State checks spread everywhere
- ✅ Easy to add states - New state = new class
- ✅ Clear state behavior - All behavior in one place
- ✅ Explicit transitions - States control transitions
- ✅ Follow Open/Closed Principle
How It Works?
Section titled “How It Works?”- Define State interface - Methods for all state behaviors
- Create Concrete States - Each state implements interface
- Create Context - Holds current state, delegates to it
- States change Context state - Transitions happen in states
- Client uses Context - Doesn’t know about states
Key Components
Section titled “Key Components”Context → State Interface → Concrete States- State - Interface for state behavior
- Concrete State - Specific state implementation
- Context - Object whose behavior changes
- Client - Uses context, unaware of states
Simple Example
Section titled “Simple Example”class State(ABC): @abstractmethod def handle(self, context): pass
class ConcreteStateA(State): def handle(self, context): print("State A behavior") context.set_state(ConcreteStateB())
class Context: def __init__(self): self._state = ConcreteStateA()
def set_state(self, state): self._state = state
def request(self): self._state.handle(self)When to Use?
Section titled “When to Use?”✅ Object behavior depends on state
✅ Many if/else checking state
✅ Well-defined state transitions
✅ Need to add states easily
✅ States should be explicit
When NOT to Use?
Section titled “When NOT to Use?”❌ Few simple states
❌ State doesn’t affect behavior
❌ Unclear transitions
❌ Over-engineering
Key Takeaways
Section titled “Key Takeaways”- State Pattern = Behavior changes with state
- State = Encapsulates state-specific behavior
- Context = Delegates to current state
- Benefit = No if/else, easy to extend
- Principle = Open for extension, closed for modification
Interview Focus: State Pattern
Section titled “Interview Focus: State Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. Instead of having if/else chains checking state, each state becomes its own class that handles behavior for that state. The object appears to change its class.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of eliminating conditionals
- Indicates you can explain concepts clearly
Must discuss:
- State - Object changes behavior based on INTERNAL state, automatic
- Strategy - Client chooses behavior EXTERNALLY, explicit
- Key difference - Who controls the change and why
Example to give:
“State Pattern is like a traffic light that automatically changes behavior based on its internal state - red means stop, green means go. Strategy Pattern is like choosing a route on GPS - YOU explicitly choose the algorithm. With State, transitions happen automatically based on internal logic. With Strategy, you explicitly set which algorithm to use.”
3. Benefits and Trade-offs
Section titled “3. Benefits and Trade-offs”Benefits to mention:
- No if/else chains - Clean, maintainable code
- Easy to add states - Just create new class
- Single Responsibility - Each state handles its behavior
- Open/Closed Principle - Add without modifying
- Explicit state machine - Code mirrors state diagram
Trade-offs to acknowledge:
- More classes - Each state is a class
- Overkill for simple states - 2-3 states might not need it
- Can be complex - Many states with many methods
4. Common Interview Questions
Section titled “4. Common Interview Questions”Q: “How does State Pattern differ from Strategy Pattern?”
A:
“Both encapsulate behavior in separate classes, but State changes behavior based on internal state automatically, while Strategy lets the client explicitly choose the algorithm. State objects typically know about other states and handle transitions. Strategy objects are usually stateless and interchangeable. State is about ‘what state am I in?’, Strategy is about ‘which algorithm should I use?’”
Q: “Where would you use State Pattern in a real system?”
A:
“I’d use State Pattern for order processing systems (pending, paid, shipped, delivered), document workflows (draft, review, published), connection handling (connecting, connected, disconnected), or UI components (enabled, disabled, loading). Any system where objects go through well-defined states with different behavior.”
Q: “How do you handle state transitions?”
A:
“State transitions can be handled in two ways: 1) States handle their own transitions - each state knows what the next state should be and calls context.setState(). 2) Context handles transitions - context decides next state based on rules. The first approach is cleaner because each state encapsulates its transition logic.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define State Pattern clearly in one sentence
- Explain when to use it (with examples)
- Compare State vs Strategy Pattern
- Implement State Pattern from scratch
- List benefits and trade-offs
- Connect to SOLID principles
- Identify when NOT to use it
- Give 2-3 real-world examples
- Discuss state transition approaches
- Draw a state diagram for an example
Remember: State Pattern is about letting objects change behavior when their state changes - no more giant if/else state machines! 🔄