Command
Intent
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Problem
A text editor needs to support copy and paste operations with undo capability. Hard-coding these operations into UI buttons creates tight coupling between the UI and business logic, and makes undo/redo difficult to implement uniformly.
Real-World Analogy
Think of ordering at a restaurant. You don’t walk into the kitchen and cook your food — you tell the waiter what you want. The waiter writes your order on a slip (the command object), takes it to the kitchen, and the chef executes it. The order can be queued, modified, or cancelled. The waiter doesn’t need to know how to cook; the slip contains everything the kitchen needs.
When You Need It
- You’re building a text editor with undo/redo — each action (type, delete, format) is a command object that knows how to execute and reverse itself
- You’re creating a task queue where jobs are submitted, stored, and executed later — possibly on a different server
- You’re implementing a macro system where users record a sequence of actions and replay them with one click
UML Class Diagram
classDiagram
class Command {
<<interface>>
+Execute() void
+Undo() void
}
class CopyCommand {
-Editor receiver
+Execute() void
+Undo() void
}
class PasteCommand {
-Editor receiver
-string previousText
+Execute() void
+Undo() void
}
class Editor {
+Clipboard string
+Content string
+Copy() void
+Paste() void
}
class EditorInvoker {
-Stack~Command~ history
+ExecuteCommand(Command) void
+UndoLastCommand() void
}
Command <|.. CopyCommand
Command <|.. PasteCommand
CopyCommand --> Editor
PasteCommand --> Editor
EditorInvoker o-- Command
Sequence Diagram
sequenceDiagram
participant Client
participant Invoker
participant Command
participant Receiver
Client->>Invoker: setCommand(cmd)
Client->>Invoker: executeCommand()
Invoker->>Command: execute()
Command->>Receiver: action()
Client->>Invoker: undo()
Invoker->>Command: undo()
Participants
| Participant | Role |
|---|---|
| Command | Declares the interface for executing and undoing an operation. |
| CopyCommand | Copies selected text to the clipboard. |
| PasteCommand | Pastes clipboard content into the editor, saving previous state for undo. |
| Editor | The receiver that performs the actual text operations. |
| EditorInvoker | Stores and executes commands, maintaining a history for undo. |
How It Works
- The client creates concrete command objects, injecting the
Editorreceiver. - Commands are passed to the
EditorInvoker, which callsExecute()and pushes the command onto a history stack. - To undo, the invoker pops the most recent command and calls
Undo().
Applicability
- You need to parameterize objects with operations.
- You need to queue, schedule, or log operations.
- You need reversible operations (undo/redo).
Trade-offs
Pros:
- Decouples the object that invokes an operation from the one that knows how to perform it
- Commands can be queued, logged, serialized, and undone
- Easy to add new commands without changing existing code
Cons:
- Can lead to a large number of small command classes — one for each action
- Simple operations may not justify the overhead of wrapping them in command objects
- Undo/redo logic can become complex for commands with side effects
Example Code
C#
public interface ICommand
{
void Execute();
void Undo();
}
public class Editor
{
public string Clipboard { get; set; } = "";
public string Content { get; set; } = "";
public void Copy() => Clipboard = Content;
public void Paste() => Content += Clipboard;
}
public class CopyCommand : ICommand
{
private readonly Editor _editor;
public CopyCommand(Editor editor) => _editor = editor;
public void Execute() => _editor.Copy();
public void Undo() { /* Copy is non-destructive */ }
}
public class PasteCommand : ICommand
{
private readonly Editor _editor;
private string _previousContent;
public PasteCommand(Editor editor) => _editor = editor;
public void Execute()
{
_previousContent = _editor.Content;
_editor.Paste();
}
public void Undo() => _editor.Content = _previousContent;
}
public class EditorInvoker
{
private readonly Stack<ICommand> _history = new();
public void ExecuteCommand(ICommand cmd)
{
cmd.Execute();
_history.Push(cmd);
}
public void UndoLastCommand()
{
if (_history.Count > 0)
_history.Pop().Undo();
}
}
// Usage
var editor = new Editor { Content = "Hello" };
var invoker = new EditorInvoker();
invoker.ExecuteCommand(new CopyCommand(editor));
invoker.ExecuteCommand(new PasteCommand(editor));
Console.WriteLine(editor.Content); // "HelloHello"
invoker.UndoLastCommand();
Console.WriteLine(editor.Content); // "Hello"
Delphi
type
ICommand = interface
procedure Execute;
procedure Undo;
end;
TEditor = class
public
Clipboard: string;
Content: string;
procedure Copy;
procedure Paste;
end;
TCopyCommand = class(TInterfacedObject, ICommand)
private
FEditor: TEditor;
public
constructor Create(AEditor: TEditor);
procedure Execute;
procedure Undo;
end;
TPasteCommand = class(TInterfacedObject, ICommand)
private
FEditor: TEditor;
FPreviousContent: string;
public
constructor Create(AEditor: TEditor);
procedure Execute;
procedure Undo;
end;
procedure TEditor.Copy;
begin
Clipboard := Content;
end;
procedure TEditor.Paste;
begin
Content := Content + Clipboard;
end;
constructor TCopyCommand.Create(AEditor: TEditor);
begin
FEditor := AEditor;
end;
procedure TCopyCommand.Execute;
begin
FEditor.Copy;
end;
procedure TCopyCommand.Undo;
begin
{ Copy is non-destructive }
end;
constructor TPasteCommand.Create(AEditor: TEditor);
begin
FEditor := AEditor;
end;
procedure TPasteCommand.Execute;
begin
FPreviousContent := FEditor.Content;
FEditor.Paste;
end;
procedure TPasteCommand.Undo;
begin
FEditor.Content := FPreviousContent;
end;
// Usage
var
Editor: TEditor;
Cmd: ICommand;
begin
Editor := TEditor.Create;
Editor.Content := 'Hello';
Cmd := TCopyCommand.Create(Editor);
Cmd.Execute;
Cmd := TPasteCommand.Create(Editor);
Cmd.Execute;
WriteLn(Editor.Content); // 'HelloHello'
Cmd.Undo;
WriteLn(Editor.Content); // 'Hello'
end;
C++
#include <iostream>
#include <memory>
#include <stack>
#include <string>
class Editor {
public:
std::string clipboard;
std::string content;
void Copy() { clipboard = content; }
void Paste() { content += clipboard; }
};
class Command {
public:
virtual ~Command() = default;
virtual void Execute() = 0;
virtual void Undo() = 0;
};
class CopyCommand : public Command {
Editor& editor_;
public:
explicit CopyCommand(Editor& e) : editor_(e) {}
void Execute() override { editor_.Copy(); }
void Undo() override { /* non-destructive */ }
};
class PasteCommand : public Command {
Editor& editor_;
std::string prev_;
public:
explicit PasteCommand(Editor& e) : editor_(e) {}
void Execute() override {
prev_ = editor_.content;
editor_.Paste();
}
void Undo() override { editor_.content = prev_; }
};
class EditorInvoker {
std::stack<std::shared_ptr<Command>> history_;
public:
void ExecuteCommand(std::shared_ptr<Command> cmd) {
cmd->Execute();
history_.push(cmd);
}
void UndoLastCommand() {
if (!history_.empty()) {
history_.top()->Undo();
history_.pop();
}
}
};
int main() {
Editor editor;
editor.content = "Hello";
EditorInvoker invoker;
invoker.ExecuteCommand(std::make_shared<CopyCommand>(editor));
invoker.ExecuteCommand(std::make_shared<PasteCommand>(editor));
std::cout << editor.content << "\n"; // HelloHello
invoker.UndoLastCommand();
std::cout << editor.content << "\n"; // Hello
return 0;
}
Runnable Examples
| Language | Source |
|---|---|
| C# | Command.cs |
| C++ | command.cpp |
| Delphi | command.pas |