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:
1# ❌ Without State Pattern - Giant if/else everywhere!2
3class Document:4 def __init__(self, content: str):5 self.content = content6 self.state = "draft" # draft, moderation, published7
8 def edit(self, new_content: str):9 # Problem: Check state in every method!10 if self.state == "draft":11 self.content = new_content12 print(f"📝 Content updated: {new_content}")13 elif self.state == "moderation":14 print("❌ Cannot edit - document is under moderation")15 elif self.state == "published":16 # Move back to draft for editing17 self.content = new_content18 self.state = "draft"19 print(f"📝 Content updated, moved back to draft")20 # What if we add a new state? Modify here!21
22 def publish(self):23 # Problem: Same state checks repeated!24 if self.state == "draft":25 self.state = "moderation"26 print("📤 Document sent to moderation")27 elif self.state == "moderation":28 self.state = "published"29 print("✅ Document published!")30 elif self.state == "published":31 print("ℹ️ Document is already published")32 # What if we add a new state? Modify here too!33
34 def reject(self):35 # Problem: And here too!36 if self.state == "draft":37 print("❌ Cannot reject - document is not in moderation")38 elif self.state == "moderation":39 self.state = "draft"40 print("🔙 Document rejected, back to draft")41 elif self.state == "published":42 print("❌ Cannot reject - document is already published")43 # Every new method needs all state checks!44
45# Problems:46# - State checks in every method47# - Adding new state = modify ALL methods48# - Hard to understand state behavior49# - Violates Open/Closed Principle1// ❌ Without State Pattern - Giant if/else everywhere!2
3public class Document {4 private String content;5 private String state = "draft"; // draft, moderation, published6
7 public Document(String content) {8 this.content = content;9 }10
11 public void edit(String newContent) {12 // Problem: Check state in every method!13 if (state.equals("draft")) {14 this.content = newContent;15 System.out.println("📝 Content updated: " + newContent);16 } else if (state.equals("moderation")) {17 System.out.println("❌ Cannot edit - document is under moderation");18 } else if (state.equals("published")) {19 // Move back to draft for editing20 this.content = newContent;21 this.state = "draft";22 System.out.println("📝 Content updated, moved back to draft");23 }24 // What if we add a new state? Modify here!25 }26
27 public void publish() {28 // Problem: Same state checks repeated!29 if (state.equals("draft")) {30 this.state = "moderation";31 System.out.println("📤 Document sent to moderation");32 } else if (state.equals("moderation")) {33 this.state = "published";34 System.out.println("✅ Document published!");35 } else if (state.equals("published")) {36 System.out.println("ℹ️ Document is already published");37 }38 // What if we add a new state? Modify here too!39 }40
41 public void reject() {42 // Problem: And here too!43 if (state.equals("draft")) {44 System.out.println("❌ Cannot reject - document is not in moderation");45 } else if (state.equals("moderation")) {46 this.state = "draft";47 System.out.println("🔙 Document rejected, back to draft");48 } else if (state.equals("published")) {49 System.out.println("❌ Cannot reject - document is already published");50 }51 // Every new method needs all state checks!52 }53}54
55// Problems:56// - State checks in every method57// - Adding new state = modify ALL methods58// - Hard to understand state behavior59// - 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"
1from abc import ABC, abstractmethod2
3# Step 1: Define the State interface4class DocumentState(ABC):5 """State interface - defines behavior for each state"""6
7 @abstractmethod8 def edit(self, document: 'Document', content: str) -> None:9 """Edit the document"""10 pass11
12 @abstractmethod13 def publish(self, document: 'Document') -> None:14 """Publish the document"""15 pass16
17 @abstractmethod18 def reject(self, document: 'Document') -> None:19 """Reject the document"""20 pass21
22 @property23 @abstractmethod24 def name(self) -> str:25 """Get state name"""26 pass27
28# Step 2: Implement Concrete States29class DraftState(DocumentState):30 """Draft state - document can be freely edited"""31
32 @property33 def name(self) -> str:34 return "Draft"35
36 def edit(self, document: 'Document', content: str) -> None:37 document._content = content38 print(f"📝 Content updated: '{content}'")39
40 def publish(self, document: 'Document') -> None:41 document.set_state(ModerationState())42 print("📤 Document sent to moderation")43
44 def reject(self, document: 'Document') -> None:45 print("❌ Cannot reject - document is not in moderation")46
47class ModerationState(DocumentState):48 """Moderation state - document is under review"""49
50 @property51 def name(self) -> str:52 return "Moderation"53
54 def edit(self, document: 'Document', content: str) -> None:55 print("❌ Cannot edit - document is under moderation")56
57 def publish(self, document: 'Document') -> None:58 document.set_state(PublishedState())59 print("✅ Document approved and published!")60
61 def reject(self, document: 'Document') -> None:62 document.set_state(DraftState())63 print("🔙 Document rejected, back to draft")64
65class PublishedState(DocumentState):66 """Published state - document is live"""67
68 @property69 def name(self) -> str:70 return "Published"71
72 def edit(self, document: 'Document', content: str) -> None:73 document._content = content74 document.set_state(DraftState())75 print(f"📝 Content updated: '{content}' - moved back to draft")76
77 def publish(self, document: 'Document') -> None:78 print("ℹ️ Document is already published")79
80 def reject(self, document: 'Document') -> None:81 print("❌ Cannot reject - document is already published")82
83# Step 3: Create the Context class84class Document:85 """Context - the document whose behavior changes based on state"""86
87 def __init__(self, content: str):88 self._content = content89 self._state: DocumentState = DraftState() # Initial state90
91 def set_state(self, state: DocumentState) -> None:92 """Change the document state"""93 old_state = self._state.name94 self._state = state95 print(f" [State: {old_state} → {state.name}]")96
97 def edit(self, content: str) -> None:98 """Edit the document - behavior depends on state"""99 print(f"\n📋 Attempting to edit in {self._state.name} state...")100 self._state.edit(self, content)101
102 def publish(self) -> None:103 """Publish the document - behavior depends on state"""104 print(f"\n📋 Attempting to publish in {self._state.name} state...")105 self._state.publish(self)106
107 def reject(self) -> None:108 """Reject the document - behavior depends on state"""109 print(f"\n📋 Attempting to reject in {self._state.name} state...")110 self._state.reject(self)111
112 @property113 def content(self) -> str:114 return self._content115
116 @property117 def state_name(self) -> str:118 return self._state.name119
120# Step 4: Use the pattern121def main():122 print("=" * 50)123 print("Document Workflow (State Pattern)")124 print("=" * 50)125
126 # Create a new document (starts in Draft state)127 doc = Document("Initial content")128 print(f"📄 Created document in {doc.state_name} state")129
130 # Edit in draft state - works!131 doc.edit("Updated content in draft")132
133 # Publish - moves to moderation134 doc.publish()135
136 # Try to edit in moderation - doesn't work!137 doc.edit("Trying to edit in moderation")138
139 # Approve (publish again) - moves to published140 doc.publish()141
142 # Try to publish again - already published143 doc.publish()144
145 # Edit published document - moves back to draft146 doc.edit("Making changes to published doc")147
148 # Publish again to go through the workflow149 doc.publish() # Draft -> Moderation150 doc.reject() # Moderation -> Draft151
152 print("\n" + "=" * 50)153 print("✅ State Pattern: Behavior changes with state!")154 print("✅ No if/else chains - each state handles itself!")155
156if __name__ == "__main__":157 main()1// Step 1: Define the State interface2interface DocumentState {3 /**4 * State interface - defines behavior for each state5 */6 void edit(Document document, String content);7 void publish(Document document);8 void reject(Document document);9 String getName();10}11
12// Step 2: Implement Concrete States13class DraftState implements DocumentState {14 /**15 * Draft state - document can be freely edited16 */17 @Override18 public String getName() {19 return "Draft";20 }21
22 @Override23 public void edit(Document document, String content) {24 document.setContent(content);25 System.out.println("📝 Content updated: '" + content + "'");26 }27
28 @Override29 public void publish(Document document) {30 document.setState(new ModerationState());31 System.out.println("📤 Document sent to moderation");32 }33
34 @Override35 public void reject(Document document) {36 System.out.println("❌ Cannot reject - document is not in moderation");37 }38}39
40class ModerationState implements DocumentState {41 /**42 * Moderation state - document is under review43 */44 @Override45 public String getName() {46 return "Moderation";47 }48
49 @Override50 public void edit(Document document, String content) {51 System.out.println("❌ Cannot edit - document is under moderation");52 }53
54 @Override55 public void publish(Document document) {56 document.setState(new PublishedState());57 System.out.println("✅ Document approved and published!");58 }59
60 @Override61 public void reject(Document document) {62 document.setState(new DraftState());63 System.out.println("🔙 Document rejected, back to draft");64 }65}66
67class PublishedState implements DocumentState {68 /**69 * Published state - document is live70 */71 @Override72 public String getName() {73 return "Published";74 }75
76 @Override77 public void edit(Document document, String content) {78 document.setContent(content);79 document.setState(new DraftState());80 System.out.println("📝 Content updated: '" + content + "' - moved back to draft");81 }82
83 @Override84 public void publish(Document document) {85 System.out.println("ℹ️ Document is already published");86 }87
88 @Override89 public void reject(Document document) {90 System.out.println("❌ Cannot reject - document is already published");91 }92}93
94// Step 3: Create the Context class95class Document {96 /**97 * Context - the document whose behavior changes based on state98 */99 private String content;100 private DocumentState state;101
102 public Document(String content) {103 this.content = content;104 this.state = new DraftState(); // Initial state105 }106
107 public void setState(DocumentState state) {108 // Change the document state109 String oldState = this.state.getName();110 this.state = state;111 System.out.println(" [State: " + oldState + " → " + state.getName() + "]");112 }113
114 public void edit(String content) {115 // Edit the document - behavior depends on state116 System.out.println("\n📋 Attempting to edit in " + state.getName() + " state...");117 state.edit(this, content);118 }119
120 public void publish() {121 // Publish the document - behavior depends on state122 System.out.println("\n📋 Attempting to publish in " + state.getName() + " state...");123 state.publish(this);124 }125
126 public void reject() {127 // Reject the document - behavior depends on state128 System.out.println("\n📋 Attempting to reject in " + state.getName() + " state...");129 state.reject(this);130 }131
132 public String getContent() {133 return content;134 }135
136 public void setContent(String content) {137 this.content = content;138 }139
140 public String getStateName() {141 return state.getName();142 }143}144
145// Step 4: Use the pattern146public class Main {147 public static void main(String[] args) {148 System.out.println("=".repeat(50));149 System.out.println("Document Workflow (State Pattern)");150 System.out.println("=".repeat(50));151
152 // Create a new document (starts in Draft state)153 Document doc = new Document("Initial content");154 System.out.println("📄 Created document in " + doc.getStateName() + " state");155
156 // Edit in draft state - works!157 doc.edit("Updated content in draft");158
159 // Publish - moves to moderation160 doc.publish();161
162 // Try to edit in moderation - doesn't work!163 doc.edit("Trying to edit in moderation");164
165 // Approve (publish again) - moves to published166 doc.publish();167
168 // Try to publish again - already published169 doc.publish();170
171 // Edit published document - moves back to draft172 doc.edit("Making changes to published doc");173
174 // Publish again to go through the workflow175 doc.publish(); // Draft -> Moderation176 doc.reject(); // Moderation -> Draft177
178 System.out.println("\n" + "=".repeat(50));179 System.out.println("✅ State Pattern: Behavior changes with state!");180 System.out.println("✅ No if/else chains - each state handles itself!");181 }182}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:
1# ❌ Without State Pattern - State checks everywhere!2
3class Order:4 def __init__(self, order_id: str, items: list, total: float):5 self.order_id = order_id6 self.items = items7 self.total = total8 self.state = "pending" # pending, paid, shipped, delivered, cancelled9
10 def pay(self, amount: float):11 if self.state == "pending":12 if amount >= self.total:13 self.state = "paid"14 print(f"✅ Order {self.order_id} paid!")15 else:16 print(f"❌ Insufficient payment: ${amount} < ${self.total}")17 elif self.state == "paid":18 print("ℹ️ Order already paid")19 elif self.state == "shipped":20 print("ℹ️ Order already shipped")21 elif self.state == "delivered":22 print("ℹ️ Order already delivered")23 elif self.state == "cancelled":24 print("❌ Cannot pay - order is cancelled")25
26 def ship(self, tracking_number: str):27 if self.state == "pending":28 print("❌ Cannot ship - order not paid")29 elif self.state == "paid":30 self.tracking_number = tracking_number31 self.state = "shipped"32 print(f"📦 Order {self.order_id} shipped! Tracking: {tracking_number}")33 elif self.state == "shipped":34 print("ℹ️ Order already shipped")35 elif self.state == "delivered":36 print("ℹ️ Order already delivered")37 elif self.state == "cancelled":38 print("❌ Cannot ship - order is cancelled")39
40 def deliver(self):41 if self.state == "pending":42 print("❌ Cannot deliver - order not paid")43 elif self.state == "paid":44 print("❌ Cannot deliver - order not shipped")45 elif self.state == "shipped":46 self.state = "delivered"47 print(f"✅ Order {self.order_id} delivered!")48 elif self.state == "delivered":49 print("ℹ️ Order already delivered")50 elif self.state == "cancelled":51 print("❌ Cannot deliver - order is cancelled")52
53 def cancel(self):54 if self.state == "pending":55 self.state = "cancelled"56 print(f"❌ Order {self.order_id} cancelled")57 elif self.state == "paid":58 self.state = "cancelled"59 print(f"❌ Order {self.order_id} cancelled - refund initiated")60 elif self.state == "shipped":61 print("❌ Cannot cancel - order already shipped")62 elif self.state == "delivered":63 print("❌ Cannot cancel - order already delivered")64 elif self.state == "cancelled":65 print("ℹ️ Order already cancelled")66
67# Problems:68# - 5 states × 4 methods = 20 if branches!69# - Adding new state = modify ALL methods70# - Adding new method = add ALL state checks1// ❌ Without State Pattern - State checks everywhere!2
3public class Order {4 private String orderId;5 private List<String> items;6 private double total;7 private String state = "pending"; // pending, paid, shipped, delivered, cancelled8 private String trackingNumber;9
10 public void pay(double amount) {11 if (state.equals("pending")) {12 if (amount >= total) {13 state = "paid";14 System.out.println("✅ Order " + orderId + " paid!");15 } else {16 System.out.println("❌ Insufficient payment: $" + amount + " < $" + total);17 }18 } else if (state.equals("paid")) {19 System.out.println("ℹ️ Order already paid");20 } else if (state.equals("shipped")) {21 System.out.println("ℹ️ Order already shipped");22 } else if (state.equals("delivered")) {23 System.out.println("ℹ️ Order already delivered");24 } else if (state.equals("cancelled")) {25 System.out.println("❌ Cannot pay - order is cancelled");26 }27 }28
29 public void ship(String trackingNumber) {30 if (state.equals("pending")) {31 System.out.println("❌ Cannot ship - order not paid");32 } else if (state.equals("paid")) {33 this.trackingNumber = trackingNumber;34 state = "shipped";35 System.out.println("📦 Order " + orderId + " shipped! Tracking: " + trackingNumber);36 } else if (state.equals("shipped")) {37 System.out.println("ℹ️ Order already shipped");38 } else if (state.equals("delivered")) {39 System.out.println("ℹ️ Order already delivered");40 } else if (state.equals("cancelled")) {41 System.out.println("❌ Cannot ship - order is cancelled");42 }43 }44
45 // ... more methods with same pattern46
47 // Problems:48 // - 5 states × 4 methods = 20 if branches!49 // - Adding new state = modify ALL methods50 // - Adding new method = add ALL state checks51}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
1from abc import ABC, abstractmethod2from typing import Optional3from dataclasses import dataclass, field4from datetime import datetime5
6# Step 1: Define the State interface7class OrderState(ABC):8 """State interface for order states"""9
10 @abstractmethod11 def pay(self, order: 'Order', amount: float) -> None:12 pass13
14 @abstractmethod15 def ship(self, order: 'Order', tracking_number: str) -> None:16 pass17
18 @abstractmethod19 def deliver(self, order: 'Order') -> None:20 pass21
22 @abstractmethod23 def cancel(self, order: 'Order') -> None:24 pass25
26 @property27 @abstractmethod28 def name(self) -> str:29 pass30
31# Step 2: Implement Concrete States32class PendingState(OrderState):33 """Pending state - awaiting payment"""34
35 @property36 def name(self) -> str:37 return "Pending"38
39 def pay(self, order: 'Order', amount: float) -> None:40 if amount >= order.total:41 order.amount_paid = amount42 order.set_state(PaidState())43 print(f"✅ Payment of ${amount:.2f} received!")44 else:45 print(f"❌ Insufficient payment: ${amount:.2f} < ${order.total:.2f}")46
47 def ship(self, order: 'Order', tracking_number: str) -> None:48 print("❌ Cannot ship - order not paid yet")49
50 def deliver(self, order: 'Order') -> None:51 print("❌ Cannot deliver - order not paid yet")52
53 def cancel(self, order: 'Order') -> None:54 order.set_state(CancelledState())55 print("🚫 Order cancelled")56
57class PaidState(OrderState):58 """Paid state - ready to ship"""59
60 @property61 def name(self) -> str:62 return "Paid"63
64 def pay(self, order: 'Order', amount: float) -> None:65 print("ℹ️ Order already paid")66
67 def ship(self, order: 'Order', tracking_number: str) -> None:68 order.tracking_number = tracking_number69 order.shipped_at = datetime.now()70 order.set_state(ShippedState())71 print(f"📦 Order shipped! Tracking: {tracking_number}")72
73 def deliver(self, order: 'Order') -> None:74 print("❌ Cannot deliver - order not shipped yet")75
76 def cancel(self, order: 'Order') -> None:77 order.set_state(CancelledState())78 print(f"🚫 Order cancelled - refund of ${order.amount_paid:.2f} initiated")79
80class ShippedState(OrderState):81 """Shipped state - in transit"""82
83 @property84 def name(self) -> str:85 return "Shipped"86
87 def pay(self, order: 'Order', amount: float) -> None:88 print("ℹ️ Order already paid")89
90 def ship(self, order: 'Order', tracking_number: str) -> None:91 print("ℹ️ Order already shipped")92
93 def deliver(self, order: 'Order') -> None:94 order.delivered_at = datetime.now()95 order.set_state(DeliveredState())96 print("✅ Order delivered successfully!")97
98 def cancel(self, order: 'Order') -> None:99 print("❌ Cannot cancel - order already shipped. Please initiate return after delivery.")100
101class DeliveredState(OrderState):102 """Delivered state - order complete"""103
104 @property105 def name(self) -> str:106 return "Delivered"107
108 def pay(self, order: 'Order', amount: float) -> None:109 print("ℹ️ Order already paid and delivered")110
111 def ship(self, order: 'Order', tracking_number: str) -> None:112 print("ℹ️ Order already delivered")113
114 def deliver(self, order: 'Order') -> None:115 print("ℹ️ Order already delivered")116
117 def cancel(self, order: 'Order') -> None:118 print("❌ Cannot cancel - order already delivered. Please initiate return.")119
120class CancelledState(OrderState):121 """Cancelled state - order terminated"""122
123 @property124 def name(self) -> str:125 return "Cancelled"126
127 def pay(self, order: 'Order', amount: float) -> None:128 print("❌ Cannot pay - order is cancelled")129
130 def ship(self, order: 'Order', tracking_number: str) -> None:131 print("❌ Cannot ship - order is cancelled")132
133 def deliver(self, order: 'Order') -> None:134 print("❌ Cannot deliver - order is cancelled")135
136 def cancel(self, order: 'Order') -> None:137 print("ℹ️ Order already cancelled")138
139# Step 3: Create the Context class140@dataclass141class OrderItem:142 """Item in an order"""143 name: str144 price: float145 quantity: int146
147class Order:148 """Context - the order whose behavior changes based on state"""149
150 def __init__(self, order_id: str, items: list[OrderItem]):151 self.order_id = order_id152 self.items = items153 self.total = sum(item.price * item.quantity for item in items)154 self.amount_paid: float = 0155 self.tracking_number: Optional[str] = None156 self.shipped_at: Optional[datetime] = None157 self.delivered_at: Optional[datetime] = None158 self._state: OrderState = PendingState()159 self.created_at = datetime.now()160
161 def set_state(self, state: OrderState) -> None:162 """Change order state"""163 old_state = self._state.name164 self._state = state165 print(f" [Order {self.order_id}: {old_state} → {state.name}]")166
167 def pay(self, amount: float) -> None:168 """Pay for the order"""169 print(f"\n💳 Processing payment of ${amount:.2f}...")170 self._state.pay(self, amount)171
172 def ship(self, tracking_number: str) -> None:173 """Ship the order"""174 print(f"\n📦 Processing shipment...")175 self._state.ship(self, tracking_number)176
177 def deliver(self) -> None:178 """Mark order as delivered"""179 print(f"\n🚚 Processing delivery...")180 self._state.deliver(self)181
182 def cancel(self) -> None:183 """Cancel the order"""184 print(f"\n🚫 Processing cancellation...")185 self._state.cancel(self)186
187 def get_status(self) -> str:188 """Get order status summary"""189 return f"""190Order: {self.order_id}191Status: {self._state.name}192Items: {len(self.items)}193Total: ${self.total:.2f}194Paid: ${self.amount_paid:.2f}195Tracking: {self.tracking_number or 'N/A'}196"""197
198# Step 4: Use the pattern199def main():200 print("=" * 60)201 print("E-Commerce Order Processing (State Pattern)")202 print("=" * 60)203
204 # Create an order205 items = [206 OrderItem("Laptop", 999.99, 1),207 OrderItem("Mouse", 29.99, 2),208 ]209 order = Order("ORD-001", items)210 print(f"📝 Created order {order.order_id}")211 print(f" Total: ${order.total:.2f}")212
213 # Try to ship before paying - should fail214 order.ship("TRACK123")215
216 # Pay for the order217 order.pay(500) # Insufficient218 order.pay(1059.97) # Exact amount219
220 # Try to pay again - already paid221 order.pay(100)222
223 # Ship the order224 order.ship("TRACK-12345-XYZ")225
226 # Try to cancel after shipping - should fail227 order.cancel()228
229 # Deliver the order230 order.deliver()231
232 # Print final status233 print("\n" + "=" * 60)234 print("Final Order Status:")235 print(order.get_status())236
237 # Demonstrate cancellation flow238 print("=" * 60)239 print("Testing Cancellation Flow:")240 print("=" * 60)241
242 order2 = Order("ORD-002", [OrderItem("Keyboard", 79.99, 1)])243 print(f"📝 Created order {order2.order_id}")244
245 order2.pay(79.99)246 order2.cancel() # Should work - refund initiated247
248 print("\n✅ State Pattern: Clean state-based behavior!")249 print("✅ Adding new state = Just one new class!")250
251if __name__ == "__main__":252 main()1import java.time.LocalDateTime;2import java.util.*;3
4// Step 1: Define the State interface5interface OrderState {6 /**7 * State interface for order states8 */9 void pay(Order order, double amount);10 void ship(Order order, String trackingNumber);11 void deliver(Order order);12 void cancel(Order order);13 String getName();14}15
16// Step 2: Implement Concrete States17class PendingState implements OrderState {18 /**19 * Pending state - awaiting payment20 */21 @Override22 public String getName() { return "Pending"; }23
24 @Override25 public void pay(Order order, double amount) {26 if (amount >= order.getTotal()) {27 order.setAmountPaid(amount);28 order.setState(new PaidState());29 System.out.printf("✅ Payment of $%.2f received!%n", amount);30 } else {31 System.out.printf("❌ Insufficient payment: $%.2f < $%.2f%n",32 amount, order.getTotal());33 }34 }35
36 @Override37 public void ship(Order order, String trackingNumber) {38 System.out.println("❌ Cannot ship - order not paid yet");39 }40
41 @Override42 public void deliver(Order order) {43 System.out.println("❌ Cannot deliver - order not paid yet");44 }45
46 @Override47 public void cancel(Order order) {48 order.setState(new CancelledState());49 System.out.println("🚫 Order cancelled");50 }51}52
53class PaidState implements OrderState {54 /**55 * Paid state - ready to ship56 */57 @Override58 public String getName() { return "Paid"; }59
60 @Override61 public void pay(Order order, double amount) {62 System.out.println("ℹ️ Order already paid");63 }64
65 @Override66 public void ship(Order order, String trackingNumber) {67 order.setTrackingNumber(trackingNumber);68 order.setShippedAt(LocalDateTime.now());69 order.setState(new ShippedState());70 System.out.println("📦 Order shipped! Tracking: " + trackingNumber);71 }72
73 @Override74 public void deliver(Order order) {75 System.out.println("❌ Cannot deliver - order not shipped yet");76 }77
78 @Override79 public void cancel(Order order) {80 order.setState(new CancelledState());81 System.out.printf("🚫 Order cancelled - refund of $%.2f initiated%n",82 order.getAmountPaid());83 }84}85
86class ShippedState implements OrderState {87 /**88 * Shipped state - in transit89 */90 @Override91 public String getName() { return "Shipped"; }92
93 @Override94 public void pay(Order order, double amount) {95 System.out.println("ℹ️ Order already paid");96 }97
98 @Override99 public void ship(Order order, String trackingNumber) {100 System.out.println("ℹ️ Order already shipped");101 }102
103 @Override104 public void deliver(Order order) {105 order.setDeliveredAt(LocalDateTime.now());106 order.setState(new DeliveredState());107 System.out.println("✅ Order delivered successfully!");108 }109
110 @Override111 public void cancel(Order order) {112 System.out.println("❌ Cannot cancel - order already shipped. Please initiate return after delivery.");113 }114}115
116class DeliveredState implements OrderState {117 /**118 * Delivered state - order complete119 */120 @Override121 public String getName() { return "Delivered"; }122
123 @Override124 public void pay(Order order, double amount) {125 System.out.println("ℹ️ Order already paid and delivered");126 }127
128 @Override129 public void ship(Order order, String trackingNumber) {130 System.out.println("ℹ️ Order already delivered");131 }132
133 @Override134 public void deliver(Order order) {135 System.out.println("ℹ️ Order already delivered");136 }137
138 @Override139 public void cancel(Order order) {140 System.out.println("❌ Cannot cancel - order already delivered. Please initiate return.");141 }142}143
144class CancelledState implements OrderState {145 /**146 * Cancelled state - order terminated147 */148 @Override149 public String getName() { return "Cancelled"; }150
151 @Override152 public void pay(Order order, double amount) {153 System.out.println("❌ Cannot pay - order is cancelled");154 }155
156 @Override157 public void ship(Order order, String trackingNumber) {158 System.out.println("❌ Cannot ship - order is cancelled");159 }160
161 @Override162 public void deliver(Order order) {163 System.out.println("❌ Cannot deliver - order is cancelled");164 }165
166 @Override167 public void cancel(Order order) {168 System.out.println("ℹ️ Order already cancelled");169 }170}171
172// Order Item class173class OrderItem {174 private String name;175 private double price;176 private int quantity;177
178 public OrderItem(String name, double price, int quantity) {179 this.name = name;180 this.price = price;181 this.quantity = quantity;182 }183
184 public double getSubtotal() {185 return price * quantity;186 }187}188
189// Step 3: Create the Context class190class Order {191 /**192 * Context - the order whose behavior changes based on state193 */194 private String orderId;195 private List<OrderItem> items;196 private double total;197 private double amountPaid;198 private String trackingNumber;199 private LocalDateTime shippedAt;200 private LocalDateTime deliveredAt;201 private OrderState state;202
203 public Order(String orderId, List<OrderItem> items) {204 this.orderId = orderId;205 this.items = items;206 this.total = items.stream().mapToDouble(OrderItem::getSubtotal).sum();207 this.amountPaid = 0;208 this.state = new PendingState();209 }210
211 public void setState(OrderState state) {212 String oldState = this.state.getName();213 this.state = state;214 System.out.println(" [Order " + orderId + ": " + oldState + " → " + state.getName() + "]");215 }216
217 public void pay(double amount) {218 System.out.printf("%n💳 Processing payment of $%.2f...%n", amount);219 state.pay(this, amount);220 }221
222 public void ship(String trackingNumber) {223 System.out.println("\n📦 Processing shipment...");224 state.ship(this, trackingNumber);225 }226
227 public void deliver() {228 System.out.println("\n🚚 Processing delivery...");229 state.deliver(this);230 }231
232 public void cancel() {233 System.out.println("\n🚫 Processing cancellation...");234 state.cancel(this);235 }236
237 // Getters and setters238 public double getTotal() { return total; }239 public double getAmountPaid() { return amountPaid; }240 public void setAmountPaid(double amount) { this.amountPaid = amount; }241 public void setTrackingNumber(String tracking) { this.trackingNumber = tracking; }242 public void setShippedAt(LocalDateTime time) { this.shippedAt = time; }243 public void setDeliveredAt(LocalDateTime time) { this.deliveredAt = time; }244 public String getOrderId() { return orderId; }245 public String getStateName() { return state.getName(); }246}247
248// Step 4: Use the pattern249public class Main {250 public static void main(String[] args) {251 System.out.println("=".repeat(60));252 System.out.println("E-Commerce Order Processing (State Pattern)");253 System.out.println("=".repeat(60));254
255 // Create an order256 List<OrderItem> items = Arrays.asList(257 new OrderItem("Laptop", 999.99, 1),258 new OrderItem("Mouse", 29.99, 2)259 );260 Order order = new Order("ORD-001", items);261 System.out.println("📝 Created order " + order.getOrderId());262 System.out.printf(" Total: $%.2f%n", order.getTotal());263
264 // Try to ship before paying - should fail265 order.ship("TRACK123");266
267 // Pay for the order268 order.pay(500); // Insufficient269 order.pay(1059.97); // Exact amount270
271 // Try to pay again - already paid272 order.pay(100);273
274 // Ship the order275 order.ship("TRACK-12345-XYZ");276
277 // Try to cancel after shipping - should fail278 order.cancel();279
280 // Deliver the order281 order.deliver();282
283 System.out.println("\n✅ State Pattern: Clean state-based behavior!");284 System.out.println("✅ Adding new state = Just one new class!");285 }286}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:
1# Singleton States - when states are stateless2class DraftState:3 _instance = None4
5 def __new__(cls):6 if cls._instance is None:7 cls._instance = super().__new__(cls)8 return cls._instance9
10 def publish(self, document):11 document.set_state(ModerationState())12
13# Usage - always same instance14state1 = DraftState()15state2 = DraftState()16assert state1 is state2 # True - same instance1// Singleton States - when states are stateless2class DraftState implements DocumentState {3 private static final DraftState INSTANCE = new DraftState();4
5 private DraftState() {}6
7 public static DraftState getInstance() {8 return INSTANCE;9 }10
11 @Override12 public void publish(Document document) {13 document.setState(ModerationState.getInstance());14 }15}16
17// Usage - always same instance18DocumentState state1 = DraftState.getInstance();19DocumentState state2 = DraftState.getInstance();20assert 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:
1# State with history tracking2class Context:3 def __init__(self):4 self._state = InitialState()5 self._state_history = []6
7 def set_state(self, state):8 # Record state transition9 self._state_history.append({10 'from': self._state.name,11 'to': state.name,12 'timestamp': datetime.now()13 })14 self._state = state15
16 def get_history(self):17 return self._state_history1// State with history tracking2class Context {3 private State state;4 private List<StateTransition> stateHistory = new ArrayList<>();5
6 public void setState(State state) {7 // Record state transition8 stateHistory.add(new StateTransition(9 this.state.getName(),10 state.getName(),11 LocalDateTime.now()12 ));13 this.state = state;14 }15
16 public List<StateTransition> getHistory() {17 return stateHistory;18 }19}3. Hierarchical States
Section titled “3. Hierarchical States”States can have sub-states:
1# Hierarchical states - states with sub-states2class ShippedState(OrderState):3 """Parent state with sub-states"""4
5 def __init__(self, sub_state: str = "in_transit"):6 self.sub_state = sub_state # in_transit, out_for_delivery7
8 def update_location(self, order, location):9 if location == "local_hub":10 self.sub_state = "out_for_delivery"11 print(f"📍 Package at local hub - out for delivery!")1// Hierarchical states - states with sub-states2class ShippedState implements OrderState {3 /**4 * Parent state with sub-states5 */6 private String subState = "in_transit"; // in_transit, out_for_delivery7
8 public void updateLocation(Order order, String location) {9 if (location.equals("local_hub")) {10 this.subState = "out_for_delivery";11 System.out.println("📍 Package at local hub - out for delivery!");12 }13 }14}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”1# ❌ Bad: State holds permanent context reference2class BadState:3 def __init__(self, context):4 self.context = context # Bad: Permanent reference5
6 def do_something(self):7 self.context.transition()8
9# ✅ Good: Context passed as parameter10class GoodState:11 def do_something(self, context): # Good: Passed as parameter12 context.transition()1// ❌ Bad: State holds permanent context reference2class BadState implements State {3 private Context context; // Bad: Permanent reference4
5 public BadState(Context context) {6 this.context = context;7 }8
9 public void doSomething() {10 context.transition();11 }12}13
14// ✅ Good: Context passed as parameter15class GoodState implements State {16 public void doSomething(Context context) { // Good: Passed as parameter17 context.transition();18 }19}Mistake 2: Context Knowing About All Concrete States
Section titled “Mistake 2: Context Knowing About All Concrete States”1# ❌ Bad: Context creates specific states2class BadContext:3 def handle(self):4 if some_condition:5 self.state = DraftState() # Bad: Context knows concrete states6 else:7 self.state = PublishedState()8
9# ✅ Good: States handle their own transitions10class GoodContext:11 def handle(self):12 self.state.handle(self) # Good: State decides next state1// ❌ Bad: Context creates specific states2class BadContext {3 public void handle() {4 if (someCondition) {5 this.state = new DraftState(); // Bad: Context knows concrete states6 } else {7 this.state = new PublishedState();8 }9 }10}11
12// ✅ Good: States handle their own transitions13class GoodContext {14 public void handle() {15 state.handle(this); // Good: State decides next state16 }17}Mistake 3: Missing Default Behavior in States
Section titled “Mistake 3: Missing Default Behavior in States”1# ❌ Bad: No default behavior - need to implement everything2class BadState(ABC):3 @abstractmethod4 def method1(self, ctx): pass5 @abstractmethod6 def method2(self, ctx): pass7 @abstractmethod8 def method3(self, ctx): pass9 # Every state must implement ALL methods!10
11# ✅ Good: Base state with default behavior12class BaseState(ABC):13 def method1(self, ctx):14 print("Operation not available in this state")15
16 def method2(self, ctx):17 print("Operation not available in this state")18
19 # States only override what they need1// ❌ Bad: No default behavior - need to implement everything2interface BadState {3 void method1(Context ctx);4 void method2(Context ctx);5 void method3(Context ctx);6 // Every state must implement ALL methods!7}8
9// ✅ Good: Abstract base state with default behavior10abstract class BaseState implements State {11 public void method1(Context ctx) {12 System.out.println("Operation not available in this state");13 }14
15 public void method2(Context ctx) {16 System.out.println("Operation not available in this state");17 }18
19 // States only override what they need20}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”1Context → 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”1class State(ABC):2 @abstractmethod3 def handle(self, context): pass4
5class ConcreteStateA(State):6 def handle(self, context):7 print("State A behavior")8 context.set_state(ConcreteStateB())9
10class Context:11 def __init__(self):12 self._state = ConcreteStateA()13
14 def set_state(self, state):15 self._state = state16
17 def request(self):18 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! 🔄