State
Behavioral Design Patterns
The Problem: The State Machine switch
Statement
Imagine you are modeling a Document
in an editor. A document can be in several states: Draft
, InReview
, and Published
. The behavior of an action like publish()
depends entirely on the current state.
A common but problematic approach is to implement this logic using a large if-else
or switch
statement inside the Document
class:
public class Document {
private String state; // "draft", "review", "published"
public void publish() {
switch (state) {
case "draft":
System.out.println("Moving to review...");
this.state = "review";
break;
case "review":
System.out.println("Publishing document...");
this.state = "published";
break;
case "published":
System.out.println("Already published. No action taken.");
break;
}
}
// ... other methods like reject(), approve() would have similar switch statements
}
This design is flawed. The Document
class becomes bloated with complex conditional logic. It violates the Single Responsibility Principle because it's managing its own data and all the complex state transition logic. It also violates the Open/Closed Principle, as adding a new state (e.g., Archived
) requires modifying every single method in this class.
Changing Behavior by Changing State
The State is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
The core idea is to encapsulate the state-specific behaviors into separate "state" objects. The main object (the "Context") holds a reference to one of these state objects, which represents its current state. When a method is called on the Context, it delegates the action to its current state object.
Crucially, the state objects themselves are responsible for handling the logic and for transitioning the Context to a new state. This elegantly organizes the code and removes the messy conditional blocks from the Context class.
The Participants
- Context: The class that has a state. It maintains a reference to a
Concrete State
object and provides a setter forState
objects to change its current state (e.g.,Document
). - State: The common interface for all state objects. It declares methods representing the actions the
Context
can perform (e.g.,publish()
). - Concrete State: A class that implements the
State
interface. It provides the implementation for the state-specific behavior and is responsible for transitioning theContext
to a new state when an action occurs (e.g.,DraftState
,ReviewState
).
Example: A Document Workflow
Let's refactor our Document
workflow using the State pattern.
// The State interface
interface IState {
void publish(Document document);
}
// The Context class
class Document {
private IState currentState;
public Document() {
// The document starts in the Draft state.
this.currentState = new DraftState();
}
// The context allows state objects to change its state.
public void changeState(IState newState) {
this.currentState = newState;
}
// The context delegates the work to its current state object.
public void publish() {
currentState.publish(this);
}
}
// Concrete State for 'Draft'
class DraftState implements IState {
@Override
public void publish(Document document) {
System.out.println("Moving document to review...");
// The state object controls the transition to the next state.
document.changeState(new ReviewState());
}
}
// Concrete State for 'In Review'
class ReviewState implements IState {
@Override
public void publish(Document document) {
System.out.println("Publishing document...");
document.changeState(new PublishedState());
}
}
// Concrete State for 'Published'
class PublishedState implements IState {
@Override
public void publish(Document document) {
System.out.println("Already published. No action taken.");
}
}
// The Client
public class Client {
public static void main(String[] args) {
Document doc = new Document();
doc.publish(); // Output: Moving document to review...
doc.publish(); // Output: Publishing document...
doc.publish(); // Output: Already published. No action taken.
}
}
Interview Focus: Analysis and Trade-offs
Benefits:
- Organizes State-Specific Logic: It perfectly adheres to the SRP by localizing all behavior related to a particular state into a single class.
- Eliminates Conditionals: It provides a clean, object-oriented solution to the problem of large
if/else
orswitch
statements based on an object's state. - Adherence to OCP: It makes it easy to introduce new states (e.g., an
ArchivedState
) without changing theDocument
context class or any of the existing state classes.
Drawbacks:
- Increased Number of Classes: If an object has many states with only minor differences in behavior, the pattern can lead to a large number of small state classes, which can add complexity.
How State Relates to Other Concepts
- State vs. Strategy (The Critical Comparison): This is the most important distinction to make in an interview. The patterns are structurally identical, but their intent is completely different.
- Strategy is about how a task is performed (the algorithm). The client is usually responsible for providing the strategy to the context. The strategies are swappable but don't typically control their own replacement.
- State is about what an object is and how it behaves in its current state. The context or the state objects themselves are responsible for the transitions between states. The client rarely, if ever, manages the state directly.
- Finite State Machines (FSM): The State pattern is a classic, object-oriented implementation of a Finite State Machine. Each
Concrete State
represents a state in the FSM, and the methods represent the transitions. - Singleton Pattern: Since state objects often have no instance variables of their own (their only purpose is to provide behavior), they are frequently implemented as Singletons to avoid creating new state objects every time a transition occurs. You can have one shared instance of
DraftState
,ReviewState
, etc.