Card 04/ 08
State Example: A Document Moving From Draft to Published
publish() means submit for review in Draft, approve in Review, and nothing in Published.
A document with a review step is a natural State, and this example maps the template onto it. The document starts as a draft. Publishing a draft submits it for review; publishing from review approves it; publishing a published document does nothing. Rejecting is possible only in review, and sends the document back to draft. Each state class writes its own transitions.
| Template | Document example |
|---|---|
| Context | Document |
| State | DocumentState, with publish and a default reject that ignores the request |
| ConcreteState | Draft, Review and Published |
| Transitions | Set by the state classes: lines 15, 21 and 25 |
from abc import ABC, abstractmethod
class DocumentState(ABC):
@abstractmethod
def publish(self, document: "Document") -> None: ...
def reject(self, document: "Document") -> None:
print(f" reject ignored in {type(self).__name__}")
class Draft(DocumentState):
def publish(self, document: "Document") -> None:
print(" submitted for review")
document.state = Review()
class Review(DocumentState):
def publish(self, document: "Document") -> None:
print(" approved and published")
document.state = Published()
def reject(self, document: "Document") -> None:
print(" sent back to draft")
document.state = Draft()
class Published(DocumentState):
def publish(self, document: "Document") -> None:
print(" already published, nothing to do")
class Document:
def __init__(self) -> None:
self.state: DocumentState = Draft()
def publish(self) -> None:
self.state.publish(self)
def reject(self) -> None:
self.state.reject(self)
doc = Document()
for action in ("publish", "reject", "publish", "publish", "publish", "reject"):
print(f"{action} in {type(doc.state).__name__}:")
getattr(doc, action)()
print("final state:", type(doc.state).__name__)publish in Draft:
submitted for review
reject in Review:
sent back to draft
publish in Draft:
submitted for review
publish in Review:
approved and published
publish in Published:
already published, nothing to do
reject in Published:
reject ignored in Published
final state: PublishedThe output follows the state diagram arrow by arrow: submitted, sent back, submitted again, approved, then nothing for the two requests that have no arrow out of Published. Document has no if in it, and neither does the loop at the bottom.
The base class gives reject a default that ignores the request, so only Review, the one state with a reject arrow, overrides it. Adding an "archived" state would be one new class and a change to Published.publish; no existing check would need finding, because there are none.
← → move through sections and cards