Command & State Patterns
Behavioral patterns allow us to manage the “how” of object interaction. The Command pattern focuses on encapsulating requests for later execution or undoing, while the State pattern focuses on how object behavior changes as its internal state evolves.
1. The Command Pattern
The Problem
If you’re building a GUI with buttons, you might have a “Save” button and a “Copy” button. You don’t want the Button class to know exactly what logic to execute (e.g., saveDatabase() or copyToClipboard()). If you hardcode this logic into the Button class, you can’t reuse it for other purposes (like a keyboard shortcut or a menu item).
The Solution
The Command pattern turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.
The setup usually involves:
- Command: Declares the interface.
- ConcreteCommand: Implements the call to the Receiver.
- Receiver: Knows how to perform the actual work (the “Logic”).
- Invoker: Triggers the command (e.g., the Button).
Java Implementation (with Undo)
// 1. Command Interface
interface Command {
void execute();
void undo();
}
// 2. Receiver
class Light {
public void on() { System.out.println("Light is ON"); }
public void off() { System.out.println("Light is OFF"); }
}
// 3. Concrete Command
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) { this.light = light; }
public void execute() { light.on(); }
public void undo() { light.off(); }
}
// 4. Invoker
class RemoteControl {
private Command command;
public void setCommand(Command command) { this.command = command; }
public void pressButton() { command.execute(); }
public void pressUndo() { command.undo(); }
}
Pros and Cons
- Pros: Decouples the object that triggers the operation from the one that knows how to perform it. Supports Undo/Redo.
- Cons: The code can become quite complicated since you’re introducing a whole new layer between senders and receivers.
2. The State Pattern
The Problem
Consider a Document class in an editor. It can be in one of three states: Draft, Moderation, or Published. In each state, the publish() method behaves differently:
- In
Draft, it moves the doc toModeration. - In
Moderation, it makes the docPublishedif the user is an admin. - In
Published, it does nothing. Using manyif-elseorswitchstatements for every method of the Document class leads to messy code that is hard to maintain.
The Solution
The State pattern lets an object alter its behavior when its internal state changes. The object will appear to change its class.
The key is to extract state-specific behaviors into separate “State” classes and delegate the work to the current state object.
Python Example
from abc import ABC, abstractmethod
# 1. State Interface
class State(ABC):
@abstractmethod
def publish(self, document):
pass
# 2. Concrete States
class DraftState(State):
def publish(self, document):
print("Moving document from Draft to Moderation.")
document.set_state(ModerationState())
class ModerationState(State):
def publish(self, document):
print("Reviewing document... Approved! Moving to Published.")
document.set_state(PublishedState())
class PublishedState(State):
def publish(self, document):
print("Document is already published. Doing nothing.")
# 3. Context (Document)
class Document:
def __init__(self):
self._state = DraftState()
def set_state(self, state: State):
self._state = state
def publish(self):
self._state.publish(self)
# Usage
doc = Document()
doc.publish() # Moves to Moderation
doc.publish() # Moves to Published
doc.publish() # Already published
Strategy vs. State
While the class diagrams look identical (a Context class holding a reference to an Interface), the intent is different:
- State: Objects know about each other and transition from one state to another.
- Strategy: Strategies are usually independent and unaware of each other; the client chooses which one to use.
Pros and Cons
- Pros: Adheres to the Single Responsibility Principle. Eliminates massive conditional state machines.
- Cons: Can be overkill if a state machine has only a few states or rarely changes.