13 The State Design Pattern
This chapter explains how an object’s behavior can depend on its internal, runtime state, and how explicitly modeling those states and transitions makes systems clearer and more robust. It motivates the need for the State Design Pattern by showing that an object must respond appropriately to both reasonable and unreasonable actions, and that its current state is determined by the values of its instance variables. Naming states and treating transitions as first-class design elements helps keep behavior predictable and testable as conditions change during execution.
A stadium ticket machine illustrates the idea with four named states (READY, VALIDATING, TICKET_SOLD, SOLD_OUT) driven by variables such as ticket count, whether a card is inserted, and the card’s validity. Customer and machine actions (insert card, check validity, take ticket, remove card) cause transitions or no-ops depending on the current state, and the system must handle even out-of-order or invalid actions without crashing or corrupting state. A straightforward, pre-pattern implementation centralizes all logic in one class with large conditional branches per action and state, which works but suffers from multiple responsibilities, weak encapsulation of state-specific behavior, and rigidity when requirements change.
Refactoring with the State Design Pattern yields an abstract State class that declares action methods, concrete state classes that encapsulate behavior and return the next state, and a context (TicketMachine) that holds the current State object and delegates to it. A small helper (StatesBlock) aggregates state instances and supplies the initial state, reducing coupling among states and between states and the context. The result is simpler context code, clear runtime state, well-encapsulated behaviors and transitions, easy handling of all actions per state, and flexibility to add, remove, or modify states with minimal impact. The chapter also relates the generic State model to this design and contrasts State with Visitor: Visitor encapsulates algorithms across different data types, while State encapsulates an object’s varying behavior across its states.
A UML state diagram showing the four states and the state transitions of an automatic ticket machine. The machine keeps track of the number of tickets it has, whether there is a credit card inserted, and the validity status of the card. Customer and machine actions (numbered, such as 1: insert card) or certain conditions (in square brackets, such as [card invalid]) can cause transitions from one state to another. This figure only shows actions that cause transitions.
Class TicketMachine is the heart of the first version of our ticket machine application. Its private instance variables keep track of the current state of the machine, the number of tickets, whether a credit card is inserted, and the validity of the credit card. The private methods implement the customer and machine actions.
The subclasses of superclass State encapsulate handling the customer and machine actions for each state of the ticket machine. Each action method of the subclasses returns a State object that is the next state for the ticket machine to transition to. By aggregating the State subclasses in class StatesBlock, the subclasses are loosely coupled with each other and with class TicketMachine. Each action method of class TicketMachine defers to the corresponding method of the current State object pointed to by its private _state instance variable.
The generic model of the State Design Pattern. Compare with figure 13.3. Instead of the Context class aggregating the State subclasses as shown above, figure 13.3 shows the State subclasses encapsulated in the StatesBlock class which allows the State subclasses to be loosely coupled with each other and with the TicketMachine class. Also in figure 13.3, each of the customer action methods returns the State object the ticket machine will transition to.
Summary
- The State Design Pattern models a solution for a software architecture where it’s important to monitor the runtime states of a particular object.
- A set of actions performed on the object causes it to behave in ways according to its current state.
- Some actions cause the object to make state transitions.
- Separate state classes each encapsulates the action behaviors of the object when it is in that state.
- Each state class must implement all the action behaviors.
Software Design for Python Programmers ebook for free