Behavioral Design Patterns
switch StatementImagine 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.
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.
Concrete State object and provides a setter for State objects to change its current state (e.g., Document).Context can perform (e.g., publish()).State interface. It provides the implementation for the state-specific behavior and is responsible for transitioning the Context to a new state when an action occurs (e.g., DraftState, ReviewState).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.
}
}
Benefits:
if/else or switch statements based on an object's state.ArchivedState) without changing the Document context class or any of the existing state classes.Drawbacks:
Concrete State represents a state in the FSM, and the methods represent the transitions.DraftState, ReviewState, etc.