Skip to main content
CodeOath
← All posts

Architecture & Patterns110 min total · 26 parts

ACID, SOLID, and Design Patterns: A Complete Software Design Reference

Part 21 of 26 · ~1 min

Behavioral Patterns: Command

Command takes an action that would normally just be a method call and turns it into an object in its own right — something storable, replayable, and reversible, with a life of its own separate from whatever triggered it in the first place.

A coordinator, moving fast through a long roster, clicks the row above the volunteer they meant to assign. Getting that undone before the confirmation text goes out matters:

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

public class AssignVolunteerCommand : ICommand
{
    private readonly ShiftRoster _roster;
    private readonly int _shiftId;
    private readonly string _volunteerId;

    public AssignVolunteerCommand(ShiftRoster roster, int shiftId, string volunteerId)
    {
        _roster = roster; _shiftId = shiftId; _volunteerId = volunteerId;
    }
    public void Execute() => _roster.Assign(_shiftId, _volunteerId);
    public void Undo() => _roster.Unassign(_shiftId, _volunteerId);
}

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

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

    public void UndoLast()
    {
        if (_history.Count > 0) _history.Pop().Undo();
    }
}

var history = new AssignmentHistory();
history.Execute(new AssignVolunteerCommand(roster, 88, "v_2291")); // wrong row, clicked in a hurry
history.UndoLast(); // unassigned before any notification ever went out

Every real editor's undo button is built on this same shape, and it turns out to solve a second, seemingly unrelated problem just as well: a job queue. Serialize an AssignVolunteerCommand or a SendDigestCommand to disk or to a message broker, and some worker process, running on a different machine, hours later, with zero context about the coordinator who created it, can deserialize it and call Execute() as if nothing about the distance in time or space mattered at all.