Command Pattern
Overview
A button may queue an edit instead of applying it immediately. The queued work needs to retain both its target and its arguments.
Command represents that request as an object. The receiver performs the actual work; the invoker calls Execute(). Here those roles belong to a text buffer and a command queue.
UML
CommandQueue stores ICommand objects. AppendText holds a TextBuffer and a string, then calls Append() when Execute() runs.
History
GoF cataloged Command as a behavioral pattern in Design Patterns in 1994. Object requests can be managed in structures such as queues and execution histories.1
Why use it?
A queue can manage execution order without knowing each edit. Callers can also create a request now and execute it later.
A command object does not supply undo or duplicate prevention automatically. Undo needs saved state or an inverse operation; commands with external effects also need an explicit re-execution policy.
Example
Construct and execute a command that appends text.
using System;
using System.Collections.Generic;
using System.Linq;
var buffer = new TextBuffer();
ICommand command = new AppendText(buffer, "A");
command.Execute();
Console.WriteLine(buffer.Text);
sealed class TextBuffer
{
public string Text { get; private set; } = "";
public void Append(string value) => Text += value;
}
interface ICommand { void Execute(); }
sealed class AppendText(TextBuffer buffer, string value) : ICommand
{
public void Execute() => buffer.Append(value);
}
sealed class CommandQueue
{
private readonly Queue<ICommand> commands = new();
public void Add(ICommand command) => commands.Enqueue(command);
public void RunAll()
{
while (commands.TryDequeue(out var command)) command.Execute();
}
}
AAppendText retains both the buffer and the string it will append.
An extended example
Queue two commands. The buffer stays empty until RunAll() executes them in order.
using System;
using System.Collections.Generic;
using System.Linq;
var buffer = new TextBuffer();
var queue = new CommandQueue();
queue.Add(new AppendText(buffer, "A"));
queue.Add(new AppendText(buffer, "B"));
Console.WriteLine("before: [" + buffer.Text + "]");
queue.RunAll();
Console.WriteLine("after: [" + buffer.Text + "]");
sealed class TextBuffer
{
public string Text { get; private set; } = "";
public void Append(string value) => Text += value;
}
interface ICommand { void Execute(); }
sealed class AppendText(TextBuffer buffer, string value) : ICommand
{
public void Execute() => buffer.Append(value);
}
sealed class CommandQueue
{
private readonly Queue<ICommand> commands = new();
public void Add(ICommand command) => commands.Enqueue(command);
public void RunAll()
{
while (commands.TryDequeue(out var command)) command.Execute();
}
}
before: []
after: [AB]This is a synchronous in-memory queue, without persistence, crash recovery or resume-after-failure behavior. Executing the same AppendText again appends its text again.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Command. β©