Type Behavioral
Purpose Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.

Description

The Command pattern is a behavioural design pattern that encapsulates a request as a stand-alone object. This command object contains all the information needed to perform the request, including the operation itself, its receiver, and any required parameters.

By turning a request into an object, the pattern makes it possible to pass requests as arguments, store them, queue them, delay their execution, or support operations such as undo and redo.

Command also decouples the sender of a request from the object that actually performs the work. The invoker does not need to know how the request is carried out; it only needs to trigger the command. This separation can make a system more flexible, easier to extend, and easier to maintain.

The pattern is commonly used in graphical user interfaces, where actions such as button clicks or menu selections can be represented as commands, but it is equally applicable in many other kinds of software.

Command is primarily concerned with encapsulating requests as objects.


Overview

Command
Client The client object creates the ConcreteCommand object and sets its receiver.
Command This is an interface for executing an operation.
ConcreteCommand Implements the Command interface, providing the Execute method that invokes an operation on the Receiver. It can also store the Receiver, command parameters, and any state required to support operations such as undo.
Receiver The Receiver class is the class of objects which will perform the action when the command is executed.
Invoker (Caller) An Invoker class is associated with the Command interface and executes the command to carry out a request. The Invoker method, when called, invokes the method of the ConcreteCommand class to perform the action.

Implementation

public class Entity
{
    public int Health { get; set; } = 5;
    public bool IsAlive => Health > 0;
}

public interface IConsumable
{
    Action Consume(Entity actor);
}

// Receiver

public class Bread : IConsumable
{
    private int _bites = 6;

    public bool HasCorrupted => _bites < 4;

    // operation that is executed when the command is invoked
    
    public Action Consume(Entity actor)
    {
        var revert = new Action(() =>
        {
            Restore();
            actor.Health -= HealthEffect;
        });
        
        actor.Health += HealthEffect;
        
        Decay();

        return revert;
    }
    
    private int HealthEffect => HasCorrupted ? -5 - _bites : 5;

    private void Restore() => ++_bites;
    
    private void Decay()
    {
        var msg = _bites switch
        {
            1 => "it is foul, not good...",
            2 => "mold has become somewhat of a condiment.",
            3 => "yuck, the bread is becoming moldy!",
            4 => "it's getting stale.",
            _ => "the bread is fresh and tasty!"
        };
        
        Console.WriteLine($"munching...{msg}");
        
        --_bites;
    }
}

public class GameState
{
    public Entity Smeagol { get; } = new ();
    public IConsumable Bread { get; } = new Bread();
}

// Command

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

// ConcreteCommand

public class ConsumeCommand(Entity actor, IConsumable item) : ICommand
{
    private Action? _revert;

    public void Execute() => _revert = item.Consume(actor);
    public void Undo() => _revert?.Invoke();
}

// Client

public class CommandSelector(GameState state)
{
    private readonly Random _random = new();
    private readonly Stack<ICommand> _history = [];
    private readonly Stack<ICommand> _undone = [];
    
    public Action? Execute()
    {
        if (state.Smeagol.IsAlive)
            return _random.Next(20) switch
            {
                < 5 => Undo(),
                < 15 => Eat(),
                _ => Redo()
            };
        
        Console.WriteLine("Smeagol has died of poisoning!");
        return Undo();
    }

    private Action Eat()
    {
        var command = new ConsumeCommand(state.Smeagol, state.Bread);
        
        _history.Push(command);
        _undone.Clear();

        return () =>
        {
            Console.Write("eats: ");
            command.Execute();
        };
    }
    
    private Action? Undo()
    {
        if (_history.Count == 0) return null;
        
        var command = _history.Pop();
        _undone.Push(command);
        
        return () =>
        {
            Console.WriteLine("undo: nasty breads hurts poor smeagol...");
            command.Undo();
        };
    }
    
    private Action? Redo()
    {
        if (_undone.Count == 0) return null;
        
        var command = _undone.Pop();
        _history.Push(command);
        
        return () =>
        {
            Console.Write("redo: give me my breadsss!...");
            command.Execute();
        };
    }
}

// Invoker (caller)

public static class CommandDemo
{
    public static void Execute()
    {
        var state = new GameState();
        var commandSelector = new CommandSelector(state);

        for (var i = 0; i < 12;)
        {   
            var action = commandSelector.Execute();
            if (action is null) continue;
            
            action.Invoke();
            ++i;
        }
    }
}

// outputs:

// eats: munching...the bread is fresh and tasty!
// undo: nasty breads hurts poor smeagol...
// redo: give me my breadsss!...munching...the bread is fresh and tasty!
// undo: nasty breads hurts poor smeagol...
// eats: munching...the bread is fresh and tasty!
// eats: munching...the bread is fresh and tasty!
// eats: munching...its getting stale.
// eats: munching...yuck, the bread is becoming moldy!
// eats: munching...mold has become somewhat of a condiment.
// eats: munching...it is foul, not good...
// Smeagol has died of poisoning!
// undo: nasty breads hurts poor smeagol...
// undo: nasty breads hurts poor smeagol...


Summary

Robert Nystrom's description of the Command Pattern is worthy reading here.

For more information, please see here.