Card 01/ 08
State Pattern: Changing Behaviour When an Object's State Changes
A document that checks if draft, if in review, if published in every method is the problem State solves.
The State pattern gives each state of an object its own class, and has the object pass every request to the class for its current state. When the state changes, the object swaps in a different state object, and its behaviour changes with it. This card covers the problem that calls for it, the intent, and when it fits.
The problem is the same conditional in every method. A document has a status field, and publish, reject and edit each begin by checking whether it is a draft, in review, or published. Adding an "archived" status means finding and changing every one of those checks, and a check missed in one method is a bug that only appears in that one state.
Without the patterneach method branches on the status
def publish(self):
if self.status == "draft":
self.status = "review"
elif self.status == "review":
self.status = "published"
def reject(self):
if self.status == "review":
self.status = "draft"With the patterneach state knows its own behaviour
class Review(DocumentState):
def publish(self, doc):
doc.state = Published()
def reject(self, doc):
doc.state = Draft()The document calls self.state.publish(self), and never checks.
The Gang of Four state the intent as: allow an object to alter its behaviour when its internal state changes, so that the object will appear to change its class. The object keeps its identity, and callers keep the same reference, but what each method does depends on the state object it holds.
A traffic light is a familiar picture. The same timer tick turns green to amber, amber to red and red to green, and what the tick does depends on the current colour. The picture breaks on who decides: a traffic light's sequence is fixed in advance, while a state object can choose between several next states depending on the request and the data.
State is one of eleven behavioural patterns, the family concerned with which object does which part of the work, and how objects communicate.
| Pattern | What it does |
|---|---|
| Chain of Responsibility | Passes a request along a chain of handlers until one handles it. |
| Command | Turns a request into an object that can be stored, queued or undone. |
| Interpreter | Represents a small language's grammar as classes and evaluates its sentences. |
| Iterator | Visits the elements of a collection without exposing how it is stored. |
| Mediator | Routes the communication between objects through one central object. |
| Memento | Captures an object's state so it can be restored later. |
| Observer | Notifies many objects when one object changes. |
| State (this topic) | Changes an object's behaviour when its internal state changes. |
| Strategy | Swaps one algorithm for another behind a common interface. |
| Template Method | Fixes an algorithm's steps in a base class and lets subclasses fill some in. |
| Visitor | Adds operations to a class hierarchy without changing its classes. |
Use it when
- Several methods branch on the same status field, and the branches keep growing.
- The object has a clear life cycle that can be drawn as a state diagram.
- Each state's behaviour is large enough that keeping it together makes it easier to read and test.
Avoid it when
- There are two or three states and one or two methods, where a single conditional is shorter.
- The states only change a value, not behaviour, so a field is enough.
- The transitions are data, such as a table loaded from configuration, which a table-driven state machine handles better.
← → move through sections and cards