You’re building a text editor with undo/redo functionality. Every action (typing, deleting, formatting) needs to be reversible. How do you track what happened and how to undo it? The Command pattern turns requests into objects, letting you parameterize clients, queue operations, and support undoable operations.
Problem
Consider a simple remote control:
public class RemoteControl
{
public void PressButton(string button)
{
if (button == "Power")
{
// Turn device on/off
}
else if (button == "VolumeUp")
{
// Increase volume
}
else if (button == "VolumeDown")
{
// Decrease volume
}
}
}
This approach has problems:
- Tight coupling between remote and devices
- Can’t easily add new buttons
- No undo functionality
- Can’t queue or log commands
- Hard to test individual operations
Solution
The Command pattern turns a request into a stand-alone object containing all information about the request. This lets you parameterize methods with different requests, queue operations, and support undo.
Definition: 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 pattern has four main components:
- Command: Interface for executing operations
- ConcreteCommand: Implements command interface, binds to receiver
- Receiver: Knows how to perform operations
- Invoker: Asks command to execute request
Pseudocode
Invoker (RemoteControl)
↓ stores/executes
Command Interface (ICommand)
↓ implemented by
ConcreteCommand (TurnOnCommand, TurnOffCommand)
↓ calls methods on
Receiver (Light, TV)
Examples
Here’s a complete, runnable C# implementation:
using System;
using System.Collections.Generic;
// Command interface
public interface ICommand
{
void Execute();
void Undo();
}
// Receiver
public class Light
{
private bool _isOn = false;
public void TurnOn()
{
_isOn = true;
Console.WriteLine("Light is ON");
}
public void TurnOff()
{
_isOn = false;
Console.WriteLine("Light is OFF");
}
public bool IsOn => _isOn;
}
// Concrete command
public class TurnOnCommand : ICommand
{
private Light _light;
public TurnOnCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
public void Undo()
{
_light.TurnOff();
}
}
public class TurnOffCommand : ICommand
{
private Light _light;
public TurnOffCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
public void Undo()
{
_light.TurnOn();
}
}
// Invoker
public class RemoteControl
{
private ICommand _command;
private Stack<ICommand> _history = new Stack<ICommand>();
public void SetCommand(ICommand command)
{
_command = command;
}
public void PressButton()
{
if (_command != null)
{
_command.Execute();
_history.Push(_command);
}
}
public void PressUndo()
{
if (_history.Count > 0)
{
var lastCommand = _history.Pop();
lastCommand.Undo();
}
}
}
// Usage
class Program
{
static void Main()
{
var light = new Light();
var remote = new RemoteControl();
// Turn on
remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();
// Turn off
remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();
// Undo
remote.PressUndo();
// Undo again
remote.PressUndo();
}
}
Output:
Light is ON
Light is OFF
Light is ON
Light is OFF
Real-World Example: Text Editor
// Receiver
public class TextEditor
{
private string _text = "";
public void Insert(string text, int position)
{
_text = _text.Insert(position, text);
Console.WriteLine($"Inserted '{text}' at position {position}");
Console.WriteLine($"Text: {_text}");
}
public void Delete(int position, int length)
{
var deleted = _text.Substring(position, length);
_text = _text.Remove(position, length);
Console.WriteLine($"Deleted '{deleted}' from position {position}");
Console.WriteLine($"Text: {_text}");
}
public string GetText() => _text;
}
// Command
public interface IEditorCommand
{
void Execute();
void Undo();
}
// Concrete commands
public class InsertCommand : IEditorCommand
{
private TextEditor _editor;
private string _text;
private int _position;
public InsertCommand(TextEditor editor, string text, int position)
{
_editor = editor;
_text = text;
_position = position;
}
public void Execute()
{
_editor.Insert(_text, _position);
}
public void Undo()
{
_editor.Delete(_position, _text.Length);
}
}
public class DeleteCommand : IEditorCommand
{
private TextEditor _editor;
private int _position;
private int _length;
private string _deletedText;
public DeleteCommand(TextEditor editor, int position, int length)
{
_editor = editor;
_position = position;
_length = length;
}
public void Execute()
{
_deletedText = _editor.GetText().Substring(_position, _length);
_editor.Delete(_position, _length);
}
public void Undo()
{
_editor.Insert(_deletedText, _position);
}
}
// Invoker
public class EditorInvoker
{
private Stack<IEditorCommand> _history = new Stack<IEditorCommand>();
public void ExecuteCommand(IEditorCommand command)
{
command.Execute();
_history.Push(command);
}
public void Undo()
{
if (_history.Count > 0)
{
var command = _history.Pop();
command.Undo();
}
}
}
// Usage
var editor = new TextEditor();
var invoker = new EditorInvoker();
invoker.ExecuteCommand(new InsertCommand(editor, "Hello", 0));
invoker.ExecuteCommand(new InsertCommand(editor, " World", 5));
invoker.Undo();
invoker.Undo();
Macro Commands Example
// Composite command
public class MacroCommand : ICommand
{
private List<ICommand> _commands = new List<ICommand>();
public void AddCommand(ICommand command)
{
_commands.Add(command);
}
public void Execute()
{
foreach (var command in _commands)
{
command.Execute();
}
}
public void Undo()
{
for (int i = _commands.Count - 1; i >= 0; i--)
{
_commands[i].Undo();
}
}
}
// Usage
var light = new Light();
var macro = new MacroCommand();
macro.AddCommand(new TurnOnCommand(light));
macro.AddCommand(new TurnOffCommand(light));
var remote = new RemoteControl();
remote.SetCommand(macro);
remote.PressButton(); // Executes all commands
Applications
Enterprise Use Cases:
- Undo/Redo Functionality: Text editors, graphics applications, transaction systems
- Macro Recording: Recording and replaying sequences of operations
- Job Queues: Queuing operations for later execution
- Transactional Systems: Implementing database transactions
- Remote Procedure Calls: Encapsulating requests for network transmission
- Logging and Auditing: Logging all operations for audit trails
- GUI Actions: Menu items, button clicks, keyboard shortcuts
- Workflow Systems: Defining and executing business processes
Benefits:
- Decouples object that invokes operation from object that performs it
- Allows you to parameterize objects with operations
- Enables undo/redo functionality
- Allows you to queue operations
- Supports logging and auditing
- Enables macro commands (composite commands)
When to Use:
- You need to parameterize objects with operations
- You need to queue operations, schedule them, or execute them remotely
- You need to support undo/redo
- You want to log operations
- You need to support transactions
Key Insight: The Command pattern encapsulates a request as an object. This allows you to parameterize clients with different requests, queue operations, log requests, and support undoable operations. Commands are first-class objects that can be stored, passed around, and executed when needed.
