Appearance
Command
Encapsulate a request as an object, thereby allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.
The Command pattern is about encapsulating a request as a command, then executing that command.
Before using the Command pattern, let's consider an example of a text editor to see how to implement simple editing operations:
java
public class TextEditor {
private StringBuilder buffer = new StringBuilder();
public void copy() {
...
}
public void paste() {
String text = getFromClipBoard();
add(text);
}
public void add(String s) {
buffer.append(s);
}
public void delete() {
if (buffer.length() > 0) {
buffer.deleteCharAt(buffer.length() - 1);
}
}
public String getState() {
return buffer.toString();
}
}
We use a StringBuilder
to simulate a text editor that supports methods like copy()
, paste()
, add()
, and delete()
.
Under normal circumstances, we would call TextEditor
like this:
java
TextEditor editor = new TextEditor();
editor.add("Command pattern in text editor.\n");
editor.copy();
editor.paste();
System.out.println(editor.getState());
This directly calls methods, requiring the caller to understand all the interfaces of TextEditor
.
If we switch to the Command pattern, we need to separate the sender of the command from the executor of the command. How do we separate them?
The solution is to introduce a Command
interface:
java
public interface Command {
void execute();
}
The caller creates a corresponding Command
and executes it without worrying about the internal execution details.
To support CopyCommand
and PasteCommand
, we derive from the Command
interface:
java
public class CopyCommand implements Command {
// Hold the receiver object:
private TextEditor receiver;
public CopyCommand(TextEditor receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.copy();
}
}
public class PasteCommand implements Command {
private TextEditor receiver;
public PasteCommand(TextEditor receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.paste();
}
}
Finally, we assemble the Command
and TextEditor
in the client as follows:
java
TextEditor editor = new TextEditor();
editor.add("Command pattern in text editor.\n");
// Execute a CopyCommand:
Command copy = new CopyCommand(editor);
copy.execute();
editor.add("----\n");
// Execute a PasteCommand:
Command paste = new PasteCommand(editor);
paste.execute();
System.out.println(editor.getState());
This is the structure of the Command pattern:
┌──────┐ ┌───────┐
│Client│─ ─ ─▶│Command│
└──────┘ └───────┘
│ ┌──────────────┐
├─▶│ CopyCommand │
│ ├──────────────┤
│ │editor.copy() │─ ┐
│ └──────────────┘
│ │ ┌────────────┐
│ ┌──────────────┐ ─▶│ TextEditor │
└─▶│ PasteCommand │ │ └────────────┘
├──────────────┤
│editor.paste()│─ ┘
└──────────────┘
Some might question: Creating a bunch of Command
classes adds multiple classes and isn't as simple as directly writing:
java
TextEditor editor = new TextEditor();
editor.add("Command pattern in text editor.\n");
editor.copy();
editor.paste();
Indeed, using the Command pattern increases the system's complexity. If the requirements are simple, direct method calls are more intuitive and straightforward.
So, do we still need the Command pattern?
The answer depends on the requirements. If the TextEditor
becomes complex enough and needs to support Undo
and Redo
functionalities, the Command pattern becomes necessary because we can add an undo()
method to each command:
java
public interface Command {
void execute();
void undo();
}
Then, we can save a series of executed commands in a List
, enabling support for Undo
and Redo
. At this point, we also need an Invoker
object responsible for executing commands and saving command history:
┌─────────────┐
│ Client │
└─────────────┘
│
│
▼
┌─────────────┐
│ Invoker │
├─────────────┤ ┌───────┐
│List commands│─ ─▶│Command│
│invoke(c) │ └───────┘
│undo() │ │ ┌──────────────┐
└─────────────┘ ├─▶│ CopyCommand │
│ ├──────────────┤
│ │editor.copy() │─ ┐
│ └──────────────┘
│ │ ┌────────────┐
│ ┌──────────────┐ ─▶│ TextEditor │
└─▶│ PasteCommand │ │ └────────────┘
├──────────────┤
│editor.paste()│─ ┘
└──────────────┘
As shown, the pattern increases the design complexity in proportion to the requirements but reduces the coupling between different components of the system.
Exercise
Add Add
and Delete
commands to the Command pattern and support Undo
and Redo
operations.
Summary
- The Command pattern's design philosophy is to separate the creation and execution of commands, allowing callers to execute commands without knowing the specific execution process.
- By encapsulating
Command
objects, the Command pattern can save executed commands, enabling operations like undo and redo.