patron-command

Command Pattern: Requests, Queues, and Undo Operations

  • 5 min

A Command is an object that encapsulates a request and the information necessary to execute it. This allows us to pass, store, or schedule it like any other data.

Think of a restaurant order. The customer places an order, the waiter delivers it, and the kitchen knows how to execute it.

In programming, we often couple the button that triggers an action too tightly with the action itself. If we put the “Save File” code inside the button’s Click event, we have a problem:

  1. We cannot reuse that logic in a keyboard shortcut.
  2. We cannot put that action in a queue to execute it later.
  3. We cannot undo it unless we save the previous state or an inverse operation.

The Command pattern proposes encapsulating a request as an independent object.

The Structure

The pattern separates four main actors:

Command (ICommand): Interface that declares the Execute() method.

Concrete Command (TurnOnLightCommand): Implements Execute. It holds a reference to the object that actually knows how to do the work (the Receiver).

Receiver (Light): The actual business object (the light bulb, the document, the engine).

Invoker (RemoteControl): The one that triggers the command (the button). It doesn’t know what the command does, it only knows to call Execute().

:::

Basic Implementation in C#

Let’s simulate a Smart Remote Control for home automation.

The Receiver

The object that does the actual work.

public class Light
{
    public void TurnOn() => Console.WriteLine("💡 Light On");
    public void TurnOff() => Console.WriteLine("🌑 Light Off");
}
Copied!

The Command Interface

public interface ICommand
{
    void Execute();
}
Copied!

The Concrete Command

This object is the “link”. It stores who needs to do something (_light) and what needs to be done (TurnOn).

public class TurnOnLightCommand : ICommand
{
    private Light _light;

    public TurnOnLightCommand(Light light)
    {
        _light = light;
    }

    public void Execute()
    {
        _light.TurnOn();
    }
}
Copied!

The Invoker

The remote control. Notice it receives an ICommand. It could be turning on a light, opening the garage, or launching a missile. The remote control doesn’t care.

public class RemoteControl
{
    private ICommand _command;

    public void SetCommand(ICommand command)
    {
        _command = command;
    }

    public void PressButton()
    {
        _command.Execute();
    }
}
Copied!

The “Killer Feature”: Undo

So far, the pattern seems to add extra complexity just to make a simple method call. But it starts to make sense when we add the Undo method.

Since the Command object knows which action was executed, it can also know how to revert it.

We modify the interface:

public interface ICommand
{
    void Execute();
    void Undo(); // The inverse operation
}
Copied!

And we update the concrete command. If executing is “Turn On”, undoing is “Turn Off”.

public class TurnOnLightCommand : ICommand
{
    private Light _light;

    public TurnOnLightCommand(Light light) => _light = light;

    public void Execute() => _light.TurnOn();
    
    public void Undo() => _light.TurnOff();
}
Copied!

Command History

Now our invoker can have memory. We store executed commands in a Stack.

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

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _history.Push(command); // Store what we just did
    }

    public void PressUndoButton()
    {
        if (_history.Count > 0)
        {
            var lastCommand = _history.Pop();
            Console.WriteLine("--- Undoing last action ---");
            lastCommand.Undo();
        }
    }
}
Copied!

Testing the System

class Program
{
    static void Main(string[] args)
    {
        // Setup
        Light livingRoomLight = new Light();
        ICommand turnOn = new TurnOnLightCommand(livingRoomLight);
        
        // Invoker
        AdvancedRemoteControl remote = new AdvancedRemoteControl();

        // 1. Turn on the light
        remote.ExecuteCommand(turnOn); 
        // Output: 💡 Light On

        // 2. We made a mistake! Ctrl+Z
        remote.PressUndoButton();
        // Output: 
        // --- Undoing last action ---
        // 🌑 Light Off
    }
}
Copied!

In real applications (like text editors), the command often saves the previous state of the object before executing the action. For example, if the command is WriteText, it stores the text that was there before to be able to restore it when doing Undo.

Other Powerful Uses: Queues and Macros

Since they are objects, commands can be placed in lists.

Macros (Composite Command)

We can create a command that contains a list of other commands. “Party Mode” = Close blinds + Turn on lights + Play music.

public class MacroCommand : ICommand
{
    private List<ICommand> _commands;

    public MacroCommand(List<ICommand> commands) => _commands = commands;

    public void Execute()
    {
        foreach (var cmd in _commands) cmd.Execute();
    }
}
Copied!

Job Queues

We can serialize commands and store them in a database to execute later, or send them to another server. This is the basis of systems like Hangfire or message queues.

Advantages and Disadvantages

AdvantagesDisadvantages
Decoupling: The requester doesn’t know the executor.Class Explosion: You need to create a new class for each action (CopyCmd, PasteCmd, CutCmd…).
Extensibility: Undo/Redo, Logging, Macros, Transactions.Complexity: For a simple button that only prints a message, it’s overkill.

Command proves especially useful in applications with complex interactions (editors, games, or design tools) and in systems that queue tasks.

It is the pattern that turns the ephemeral action of “doing something” into a tangible object that we can save, undo, and manage.