Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 to HasMoneyState. press_button() should print "Please insert money first."
  • HasMoneyState: insert_money() should print "Money already inserted." press_button() should transition the machine to DispensingState.
  • DispensingState: insert_money() should print "Wait, I'm already dispensing!" press_button() should print "Dispensing in progress..." and then transition the machine back to IdleState.

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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.