State Pattern — Object Behavior Changes with Internal State
What You'll Learn
You will learn how the State pattern models state-specific behaviour in separate classes, eliminating massive switch-case or if-else conditionals in your domain objects. You will see how State resembles Strategy but with the crucial difference that State objects manage their own transitions.
Why It Matters
A document can be in Draft, Moderation, Published, or Archived states. The actions "publish", "edit", or "archive" behave differently in each state. Without State, you end up with methods full of conditional logic that grow with every new state. State pushes each state's behaviour into its own class. Adding a new state like "Review" means adding a new class without touching Document, DraftState, PublishedState, or any other existing state.
Real-World Use
TCP connections respond differently to open, close, send, and receive depending on whether they are in LISTEN, SYN_SENT, ESTABLISHED, or CLOSE_WAIT state. DodaTech's deployment pipeline models environments as states — Development, Staging, Production — each with different deployment rules. A deploy command behaves differently in Development (deploy directly) than in Production (require approval), and the state objects encapsulate this logic.
The Pattern
Context maintains a reference to the current State object. State declares the interface for state-specific behaviour. ConcreteStates implement behaviour and may transition the context to another state.
from abc import ABC, abstractmethod
class DocumentState(ABC):
@abstractmethod
def publish(self, doc: "Document") -> str:
pass
@abstractmethod
def edit(self, doc: "Document", content: str) -> str:
pass
class DraftState(DocumentState):
def publish(self, doc: "Document") -> str:
doc._state = ModerationState()
return "Submitted for moderation"
def edit(self, doc: "Document", content: str) -> str:
doc._content = content
return f"Edited draft: '{content}'"
class ModerationState(DocumentState):
def publish(self, doc: "Document") -> str:
doc._state = PublishedState()
return "Document published"
def edit(self, doc: "Document", content: str) -> str:
return "Cannot edit while in moderation"
class PublishedState(DocumentState):
def publish(self, doc: "Document") -> str:
return "Already published"
def edit(self, doc: "Document", content: str) -> str:
return "Published documents cannot be edited"
class Document:
def __init__(self):
self._state = DraftState()
self._content = ""
def publish(self) -> str:
return self._state.publish(self)
def edit(self, content: str) -> str:
return self._state.edit(self, content)
doc = Document()
print(doc.edit("Hello World"))
print(doc.publish())
print(doc.edit("New content"))
print(doc.publish())
print(doc.publish())
Edited draft: 'Hello World'
Submitted for moderation
Cannot edit while in moderation
Document published
Already published
Structure
classDiagram
class Context {
-state: State
+request()
}
class State {
<>
+handle(context)
}
class ConcreteStateA {
+handle(context)
}
class ConcreteStateB {
+handle(context)
}
class ConcreteStateC {
+handle(context)
}
Context --> State : delegates
ConcreteStateA ..|> State
ConcreteStateB ..|> State
ConcreteStateC ..|> State
ConcreteStateA --> Context : transitions
ConcreteStateB --> Context : transitions
ConcreteStateC --> Context : transitions
Real-World Usage
- Java
javax.faces.lifecycle.Lifecycle— JSF lifecycle phases (Restore View, Apply Request, Process Events, etc.) are implemented as states. - Python
asyncio.Task— task transitions through PENDING, RUNNING, CANCELLED, FINISHED states. - Traffic light systems — lights transition between RED, GREEN, YELLOW with timing rules.
- Vending machines — idle, collecting money, dispensing item, and returning change states.
Related Patterns
- Strategy uses the same structure but the client sets the algorithm; State transitions itself.
- Memento can save and restore state objects for checkpointing.
- Singleton often makes state instances shareable since they have no internal state of their own.
- Flyweight shares stateless state objects across multiple contexts.
Pros and Cons
| Pros | Cons |
|---|---|
| Eliminates massive conditionals | Increases number of classes |
| Each state is isolated and testable | State transitions scatter across classes |
| New states added without changing existing states | Context can become coupled to state classes |
| State transitions are explicit and visible | For simple states, the overhead is unnecessary |
The document workflow demonstrates how clean the State pattern is. Each state class knows exactly which transitions are valid. A document in DraftState can be edited and published. In ModerationState, editing is blocked but publishing is allowed. In PublishedState, both editing and publishing have clear feedback. The context (Document) never contains a single if-statement.
Practice Questions
- Implement an order processing system with states: New, Paid, Shipped, Delivered, Cancelled.
- How would you persist the current state of a context so it survives application restarts?
- Compare State with Strategy — when do you let the object decide (State), and when does the client decide (Strategy)?
- Implement a state machine against a hierarchical state pattern (nested states) where a parent state can handle events not handled by its child.
Challenge
Add an ArchivedState to the document workflow. Archived documents cannot be edited or published. What transitions are valid from Archived? Should a document ever leave the Archived state?
Real-World Task
Identify a class in your application that contains complex conditional logic based on an enum or string field. Extract each branch into a State class and use DodaTech's state machine visualiser to plot the full transition graph.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro