Skip to content
Sponsor: NimblePros Software architecture, modernization, and training for high-performing development teams. Work with us.
Command Design Pattern

Command Design Pattern

What is the Command Design Pattern?

The Command Design Pattern is a behavioral pattern that turns a request into a stand-alone object containing all information about the request. This transformation lets you:

  • Pass requests as method arguments.
  • Delay or queue the execution of a request.
  • Support undoable and redoable operations.
  • Build macro commands that execute a sequence of operations.
  • Log and replay requests.

The pattern involves four participants:

  • Command — an interface (or abstract class) that declares an Execute method (and optionally an Undo method).
  • ConcreteCommand — implements the Command interface and holds a reference to a Receiver.
  • Receiver — the object that knows how to carry out the actual work.
  • Invoker — asks the command to execute the request; it does not need to know anything about the concrete command or receiver.

The Command pattern is commonly used alongside the Mediator pattern and is central to the CQRS (Command Query Responsibility Segregation) pattern.

C# Example

The following example models a simple text editor that supports typed characters with undo support.

Command Interface

public interface ICommand
{
    void Execute();
    void Undo();
}

Receiver

public class TextEditor
{
    private readonly System.Text.StringBuilder _content = new();

    public void Type(string text) => _content.Append(text);

    public void DeleteLast(int length)
    {
        if (length > _content.Length) length = _content.Length;
        _content.Remove(_content.Length - length, length);
    }

    public string GetContent() => _content.ToString();
}

Concrete Command

public class TypeTextCommand : ICommand
{
    private readonly TextEditor _editor;
    private readonly string _text;

    public TypeTextCommand(TextEditor editor, string text)
    {
        _editor = editor;
        _text = text;
    }

    public void Execute() => _editor.Type(_text);

    public void Undo() => _editor.DeleteLast(_text.Length);
}

Invoker

public class CommandHistory
{
    private readonly Stack<ICommand> _history = new();

    public void Execute(ICommand command)
    {
        command.Execute();
        _history.Push(command);
    }

    public void Undo()
    {
        if (_history.TryPop(out var command))
        {
            command.Undo();
        }
    }
}

Usage

var editor = new TextEditor();
var history = new CommandHistory();

history.Execute(new TypeTextCommand(editor, "Hello, "));
history.Execute(new TypeTextCommand(editor, "World!"));

Console.WriteLine(editor.GetContent()); // Hello, World!

history.Undo();
Console.WriteLine(editor.GetContent()); // Hello, 

history.Undo();
Console.WriteLine(editor.GetContent()); // (empty)

The CommandHistory invoker knows nothing about what a command does — it simply calls Execute and Undo. New commands (e.g., BoldTextCommand, DeleteWordCommand) can be added without changing the invoker or the editor.

Traditional vs. Modern Command Approaches

In the traditional GoF formulation, the command object itself contains the execution logic — the Execute method lives on the command, and the command holds a reference to the receiver that carries out the work.

Many modern message-passing and application frameworks take a different approach: the command is a plain data-transfer object (DTO) that carries only the data describing the request (no behavior), and a separate CommandHandler is responsible for executing the logic. This separates what to do (the command DTO) from how to do it (the handler), which makes commands easy to serialize, queue, and transmit across process or service boundaries.

// Command as a DTO — no Execute() method, just data
public record PlaceOrderCommand(Guid CustomerId, IReadOnlyList<OrderLine> Lines);

// Handler owns all the execution logic
public class PlaceOrderCommandHandler
{
    private readonly IOrderRepository _orders;

    public PlaceOrderCommandHandler(IOrderRepository orders)
    {
        _orders = orders;
    }

    public async Task HandleAsync(PlaceOrderCommand command)
    {
        var order = Order.Place(command.CustomerId, command.Lines);
        await _orders.AddAsync(order);
    }
}

Frameworks like MediatR and Wolverine use this handler-based approach. The dispatcher (often built on the Mediator pattern) receives a command DTO and routes it to the appropriate handler. Cross-cutting concerns such as logging, validation, authorization, and retry logic are typically layered around handlers using the Chain of Responsibility pattern (pipeline behaviors or middleware), rather than being embedded in the command itself.

Both approaches honor the core intent of the Command pattern — encapsulating a request as an object — but the DTO approach prioritizes loose coupling and infrastructure flexibility over the self-contained command objects of the original GoF definition.

Intent

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. GoF

References

Pluralsight - Design Patterns Library

Amazon - Design Patterns: Elements of Reusable Object-Oriented Software - Gang of Four