State
Intent
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Problem
A document in a workflow system can be in Draft, Review, or Published state. Each state dictates which operations are allowed (edit, submit for review, publish). Using conditionals to check the current state throughout the code leads to complex, hard-to-maintain logic.
Real-World Analogy
Think of a vending machine. When it’s “waiting for money,” pressing the item button does nothing. Once you insert enough coins, pressing the button dispenses the item. After dispensing, it goes back to waiting. The same button press does different things depending on the machine’s current state. The machine’s behavior changes as its state changes — without rewiring the buttons.
When You Need It
- You’re building a document workflow where a document goes through Draft, Review, Approved, and Published states — and the available actions (edit, submit, approve, reject) depend on which state it’s in
- You’re implementing a TCP connection that behaves differently when it’s listening, connected, or closing — and you want to avoid giant if/else chains
- You’re creating a media player where play, pause, and stop buttons behave differently depending on whether a track is playing, paused, or stopped
UML Class Diagram
classDiagram
class Document {
-IDocumentState state
+SetState(IDocumentState) void
+Edit() void
+Submit() void
+Publish() void
}
class IDocumentState {
<<interface>>
+Edit(Document) void
+Submit(Document) void
+Publish(Document) void
}
class DraftState {
+Edit(Document) void
+Submit(Document) void
+Publish(Document) void
}
class ReviewState {
+Edit(Document) void
+Submit(Document) void
+Publish(Document) void
}
class PublishedState {
+Edit(Document) void
+Submit(Document) void
+Publish(Document) void
}
Document o-- IDocumentState
IDocumentState <|.. DraftState
IDocumentState <|.. ReviewState
IDocumentState <|.. PublishedState
Sequence Diagram
sequenceDiagram
participant Client
participant Context
participant State
Client->>Context: request()
Context->>State: handle()
State->>Context: setState(newState)
Participants
| Participant | Role |
|---|---|
| Document | Context that maintains a reference to the current state and delegates behavior to it. |
| IDocumentState | Interface declaring state-specific behavior. |
| DraftState | Allows editing and submission. |
| ReviewState | Allows publishing; rejects edits. |
| PublishedState | Final state; no further transitions. |
How It Works
- The
Documentstarts inDraftState. - Operations are delegated to the current state object.
- Each state handles the operation and may transition the document to a new state by calling
SetState().
Applicability
- An object’s behavior depends on its state, and it must change behavior at runtime.
- Operations contain large conditional statements that depend on state.
Trade-offs
Pros:
- Eliminates large conditional statements (if/switch) for state-dependent behavior
- Each state is encapsulated in its own class, following Single Responsibility
- Adding new states doesn’t require modifying existing state classes
Cons:
- Can be overkill for objects with only two or three simple states
- Increases the number of classes in the system
- State transitions can be hard to track when spread across multiple state classes
Example Code
C#
public interface IDocumentState
{
void Edit(Document doc);
void Submit(Document doc);
void Publish(Document doc);
}
public class Document
{
public IDocumentState State { get; set; }
public Document() => State = new DraftState();
public void Edit() => State.Edit(this);
public void Submit() => State.Submit(this);
public void Publish() => State.Publish(this);
}
public class DraftState : IDocumentState
{
public void Edit(Document doc) => Console.WriteLine("Editing draft.");
public void Submit(Document doc)
{
Console.WriteLine("Submitting for review.");
doc.State = new ReviewState();
}
public void Publish(Document doc) => Console.WriteLine("Cannot publish a draft.");
}
public class ReviewState : IDocumentState
{
public void Edit(Document doc) => Console.WriteLine("Cannot edit during review.");
public void Submit(Document doc) => Console.WriteLine("Already in review.");
public void Publish(Document doc)
{
Console.WriteLine("Publishing document.");
doc.State = new PublishedState();
}
}
public class PublishedState : IDocumentState
{
public void Edit(Document doc) => Console.WriteLine("Cannot edit published document.");
public void Submit(Document doc) => Console.WriteLine("Already published.");
public void Publish(Document doc) => Console.WriteLine("Already published.");
}
// Usage
var doc = new Document();
doc.Edit(); // Editing draft.
doc.Submit(); // Submitting for review.
doc.Publish(); // Publishing document.
doc.Edit(); // Cannot edit published document.
Delphi
type
TDocument = class;
IDocumentState = interface
procedure Edit(ADoc: TDocument);
procedure Submit(ADoc: TDocument);
procedure Publish(ADoc: TDocument);
end;
TDocument = class
public
State: IDocumentState;
constructor Create;
procedure Edit;
procedure Submit;
procedure Publish;
end;
TDraftState = class(TInterfacedObject, IDocumentState)
public
procedure Edit(ADoc: TDocument);
procedure Submit(ADoc: TDocument);
procedure Publish(ADoc: TDocument);
end;
TReviewState = class(TInterfacedObject, IDocumentState)
public
procedure Edit(ADoc: TDocument);
procedure Submit(ADoc: TDocument);
procedure Publish(ADoc: TDocument);
end;
TPublishedState = class(TInterfacedObject, IDocumentState)
public
procedure Edit(ADoc: TDocument);
procedure Submit(ADoc: TDocument);
procedure Publish(ADoc: TDocument);
end;
constructor TDocument.Create;
begin
State := TDraftState.Create;
end;
procedure TDocument.Edit;
begin
State.Edit(Self);
end;
procedure TDocument.Submit;
begin
State.Submit(Self);
end;
procedure TDocument.Publish;
begin
State.Publish(Self);
end;
procedure TDraftState.Edit(ADoc: TDocument);
begin
WriteLn('Editing draft.');
end;
procedure TDraftState.Submit(ADoc: TDocument);
begin
WriteLn('Submitting for review.');
ADoc.State := TReviewState.Create;
end;
procedure TDraftState.Publish(ADoc: TDocument);
begin
WriteLn('Cannot publish a draft.');
end;
procedure TReviewState.Edit(ADoc: TDocument);
begin
WriteLn('Cannot edit during review.');
end;
procedure TReviewState.Submit(ADoc: TDocument);
begin
WriteLn('Already in review.');
end;
procedure TReviewState.Publish(ADoc: TDocument);
begin
WriteLn('Publishing document.');
ADoc.State := TPublishedState.Create;
end;
procedure TPublishedState.Edit(ADoc: TDocument);
begin
WriteLn('Cannot edit published document.');
end;
procedure TPublishedState.Submit(ADoc: TDocument);
begin
WriteLn('Already published.');
end;
procedure TPublishedState.Publish(ADoc: TDocument);
begin
WriteLn('Already published.');
end;
// Usage
var
Doc: TDocument;
begin
Doc := TDocument.Create;
Doc.Edit; // Editing draft.
Doc.Submit; // Submitting for review.
Doc.Publish; // Publishing document.
Doc.Edit; // Cannot edit published document.
end;
C++
#include <iostream>
#include <memory>
class Document;
class IDocumentState {
public:
virtual ~IDocumentState() = default;
virtual void Edit(Document& doc) = 0;
virtual void Submit(Document& doc) = 0;
virtual void Publish(Document& doc) = 0;
};
class Document {
public:
std::shared_ptr<IDocumentState> state;
Document();
void Edit() { state->Edit(*this); }
void Submit() { state->Submit(*this); }
void Publish() { state->Publish(*this); }
};
class PublishedState : public IDocumentState {
public:
void Edit(Document&) override { std::cout << "Cannot edit published document.\n"; }
void Submit(Document&) override { std::cout << "Already published.\n"; }
void Publish(Document&) override { std::cout << "Already published.\n"; }
};
class ReviewState : public IDocumentState {
public:
void Edit(Document&) override { std::cout << "Cannot edit during review.\n"; }
void Submit(Document&) override { std::cout << "Already in review.\n"; }
void Publish(Document& doc) override {
std::cout << "Publishing document.\n";
doc.state = std::make_shared<PublishedState>();
}
};
class DraftState : public IDocumentState {
public:
void Edit(Document&) override { std::cout << "Editing draft.\n"; }
void Submit(Document& doc) override {
std::cout << "Submitting for review.\n";
doc.state = std::make_shared<ReviewState>();
}
void Publish(Document&) override { std::cout << "Cannot publish a draft.\n"; }
};
Document::Document() : state(std::make_shared<DraftState>()) {}
int main() {
Document doc;
doc.Edit(); // Editing draft.
doc.Submit(); // Submitting for review.
doc.Publish(); // Publishing document.
doc.Edit(); // Cannot edit published document.
return 0;
}