Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
377: The State Pattern
I've seen this pattern a hundred times in legacy codebases, and it almost always starts the same way. You have a class that represents some kind of process—a payment, an order, a user account—and it has a "status" field. At first, two or three if statements feel manageable. But then the business requirements grow, and suddenly your methods look like a wall of elif blocks.
Take a look at this Document workflow. It's a classic example of what I call the "Conditional Nightmare."
class Document:
def __init__(self):
self.state = "DRAFT"
def publish(self):
if self.state == "DRAFT":
print("Sending to review...")
self.state = "REVIEW"
elif self.state == "REVIEW":
print("Publishing to live site!")
self.state = "PUBLISHED"
elif self.state == "PUBLISHED":
print("Already published. Doing nothing.")
def edit(self):
if self.state == "DRAFT":
print("Editing draft...")
elif self.state == "REVIEW":
print("Returning to draft for edits.")
self.state = "DRAFT"
elif self.state == "PUBLISHED":
print("Cannot edit a published document!")
The "If-Else" Explosion
On the surface, this works. But imagine we need to add a "Scheduled" state, an "Archived" state, and a "Rejected" state. Now, every single method in the Document class has to be updated with new elif branches. One small typo in a string like "REVIEW" (maybe you write "REVIEWING") and your entire workflow silently breaks.
The fundamental problem here is that the Document class is trying to be two things at once: it's holding the document data, and it's managing the complex business logic for every possible state it could ever be in. It's violating the Single Responsibility Principle, and it's making your code brittle.
Delegating Behavior to State Objects
The fix is to stop treating the state as a simple string and start treating it as an object. In the State Pattern, we move the state-specific logic out of the main class and into its own dedicated classes. The main class (the "Context") then simply delegates the work to whichever state object is currently active.
Here is how I would refactor this using an abstract base class to keep things clean:
from abc import ABC, abstractmethod
class State(ABC):
@abstractmethod
def publish(self, doc):
pass
@abstractmethod
def edit(self, doc):
pass
class DraftState(State):
def publish(self, doc):
print("Sending to review...")
doc.set_state(ReviewState())
def edit(self, doc):
print("Editing draft...")
class ReviewState(State):
def publish(self, doc):
print("Publishing to live site!")
doc.set_state(PublishedState())
def edit(self, doc):
print("Returning to draft for edits.")
doc.set_state(DraftState())
class PublishedState(State):
def publish(self, doc):
print("Already published. Doing nothing.")
def edit(self, doc):
print("Cannot edit a published document!")
class Document:
def __init__(self):
self._state = DraftState()
def set_state(self, state):
self._state = state
def publish(self):
self._state.publish(self)
def edit(self):
self._state.edit(self)
Why this actually helps you
Notice what happened to the Document class. It shrunk. It no longer cares how publishing works; it just knows that its current state handles it. If you need to add an "Archived" state tomorrow, you don't touch the Document class or the DraftState class. You just create a new ArchivedState class and define its specific rules.
I'll give you a pro tip: passing the doc instance into the state methods (like self._state.publish(self)) is crucial. It allows the state object to trigger a state transition on the context. Without that reference, the state object would be a dead end—it could perform an action, but it couldn't tell the document to move to the next phase.
📋 Practical Task
Implementation: Vending Machine State Logic
You are building a Vending Machine controller. The machine has three states: IdleState (waiting for money), HasMoneyState (money inserted, waiting for selection), and DispensingState (releasing the product).
Your task is to implement the State Pattern to handle these transitions. The machine should have two main methods: insert_money() and press_button().
- IdleState:
insert_money()should transition the machine toHasMoneyState.press_button()should print "Please insert money first." - HasMoneyState:
insert_money()should print "Money already inserted."press_button()should transition the machine toDispensingState. - DispensingState:
insert_money()should print "Wait, I'm already dispensing!"press_button()should print "Dispensing in progress..." and then transition the machine back toIdleState.
Create a VendingMachine context class that manages the current state and delegates these two methods to the state objects. Ensure you use an abstract base class for the states to enforce the interface.
There are no comments for now.