Commanding Actions: A Comprehensive Guide to the Command Pattern in C#

The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object, containing all the information about the request. This object can then be passed around, stored, or executed at a later time. The pattern allows decoupling between the sender of a request and the object that processes the request, providing flexibility and extensibility to the system. In this article, we will explore the Command Pattern in-depth, examining its structure, advantages, and providing practical examples in C#.

Understanding the Command Pattern

The Command Pattern involves the following key components:

  1. Command: The interface or abstract class that declares a method for executing a particular operation.
  2. ConcreteCommand: The class that implements the Command interface and encapsulates a request as an object. It typically contains a reference to the object that will execute the request (the Receiver).
  3. Invoker: The class that asks the command to execute the request. It does not know how the request is executed, only that it has a command.
  4. Receiver: The class that knows how to perform the operation associated with the request.
  5. Client: The class that creates the Command and associates it with the Receiver.

Implementation in C#

Let's delve into a simple example of the Command Pattern in C#. Suppose we have a home automation system where we want to control electronic devices using remote controls. The Command Pattern can be applied to represent commands and decouple the sender from the receiver.

// Step 1: Define Command interface
public interface ICommand
{
    void Execute();
}

// Step 2: Implement ConcreteCommands
public class LightOnCommand : ICommand
{
    private readonly Light light;

    public LightOnCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOn();
    }
}

public class LightOffCommand : ICommand
{
    private readonly Light light;

    public LightOffCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOff();
    }
}

// Step 3: Implement Receiver
public class Light
{
    public void TurnOn()
    {
        Console.WriteLine("Light is ON");
    }

    public void TurnOff()
    {
        Console.WriteLine("Light is OFF");
    }
}

// Step 4: Implement Invoker
public class RemoteControl
{
    private ICommand command;

    public void SetCommand(ICommand command)
    {
        this.command = command;
    }

    public void PressButton()
    {
        command.Execute();
    }
}

// Step 5: Implement Client
public class Client
{
    public void Run()
    {
        Light livingRoomLight = new Light();
        ICommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        ICommand livingRoomLightOff = new LightOffCommand(livingRoomLight);

        RemoteControl remote = new RemoteControl();
        remote.SetCommand(livingRoomLightOn);

        // Press the button to turn on the living room light
        remote.PressButton();

        remote.SetCommand(livingRoomLightOff);

        // Press the button to turn off the living room light
        remote.PressButton();
    }
}

In this example, ICommand is the command interface that declares the Execute method. LightOnCommand and LightOffCommand are concrete command classes that implement the ICommand interface and encapsulate requests to turn the light on and off, respectively. The Light class is the receiver that knows how to perform the operations. The RemoteControl class is the invoker that asks the command to execute the request, and the Client class creates the command objects and associates them with the receivers.

Advantages of the Command Pattern

1. Decoupling: The Command Pattern decouples the sender of a request from the object that processes the request, allowing for flexibility in the system.

2. Undo Operations: The pattern supports undo operations by storing the state of the system before executing a command and allowing the reversal of the command.

3. Queueing Requests: Commands can be queued, delayed, or logged for future execution, providing additional flexibility in managing requests.

4. Extensibility: It is easy to add new commands without modifying existing code. This promotes an open/closed principle.

Real-world Examples

1. Text Editors (Undo/Redo Functionality)

In text editors, the Command Pattern is commonly used to implement undo and redo functionality. Each command represents an editing operation (e.g., typing, deleting, formatting), and the editor keeps a history of executed commands, allowing users to undo or redo actions.

// Simplified example in C#
public interface IEditorCommand
{
    void Execute();
    void Undo();
}

public class TypeCommand : IEditorCommand
{
    private readonly Editor editor;
    private readonly string text;

    public TypeCommand(Editor editor, string text)
    {
        this.editor = editor;
        this.text = text;
    }

    public void Execute()
    {
        editor.Type(text);
    }

    public void Undo()
    {
        editor.Delete(text.Length);
    }
}

public class DeleteCommand : IEditorCommand
{
    private readonly Editor editor;
    private readonly int length;

    public DeleteCommand(Editor editor, int length)
    {
        this.editor = editor;
        this.length = length;
    }

    public void Execute()
    {
        editor.Delete(length);
    }

    public void Undo()
    {
        editor.Type(new string('X', length));
    }
}

public class Editor
{
    private string content = "";

    public void Type(string text)
    {
        content += text;
        Console.WriteLine($"Typed: {text}");
    }

    public void Delete(int length)
    {
        if (length <= content.Length)
        {
            content = content.Substring(0, content.Length - length);
            Console.WriteLine($"Deleted: {length} characters");
        }
        else
        {
            Console.WriteLine("Cannot delete beyond the beginning of the text.");
        }
    }

    public void DisplayContent()
    {
        Console.WriteLine($"Editor Content: {content}");
    }
}

public class EditorClient
{
    public void Run()
    {
        Editor editor = new Editor();
        CommandInvoker invoker = new CommandInvoker();

        // Type and display
        invoker.ExecuteCommand(new TypeCommand(editor, "Hello, "));
        editor.DisplayContent();

        // Type and display
        invoker.ExecuteCommand(new TypeCommand(editor, "Command Pattern!"));
        editor.DisplayContent();

        // Undo and display
        invoker.Undo();
        editor.DisplayContent();
    }
}

2. Remote Controls for Smart Home Devices

In a smart home system, the Command Pattern can be applied to control various devices (lights, thermostats, etc.) using remote controls. Each command represents an action (e.g., turn on, turn off, set temperature), and the remote control invokes the corresponding command.

// Simplified example in C#
public interface IDeviceCommand
{
    void Execute();
}

public class LightOnCommand : IDeviceCommand
{
    private readonly Light light;

    public LightOnCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOn();
    }
}

public class LightOffCommand : IDeviceCommand
{
    private readonly Light light;

    public LightOffCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOff();
    }
}

public class ThermostatSetTemperatureCommand : IDeviceCommand
{
    private readonly Thermostat thermostat;
    private readonly int temperature;

    public ThermostatSetTemperatureCommand(Thermostat thermostat, int temperature)
    {
        this.thermostat = thermostat;
        this.temperature = temperature;
    }

    public void Execute()
    {
        thermostat.SetTemperature(temperature);
    }
}

public class Light
{
    public void TurnOn()
    {
        Console.WriteLine("Light is ON");
    }

    public void TurnOff()
    {
        Console.WriteLine("Light is OFF");
    }
}

public class Thermostat
{
    private int currentTemperature = 20;

    public void SetTemperature(int temperature)
    {
        currentTemperature = temperature;
        Console.WriteLine($"Thermostat set to {temperature}°C");
    }
}

public class RemoteControl
{
    private IDeviceCommand command;

    public void SetCommand(IDeviceCommand command)
    {
        this.command = command;
    }

    public void PressButton()
    {
        command.Execute();
    }
}

Conclusion

The Command Pattern is a powerful tool for encapsulating requests as objects and promoting decoupling in a system. Through practical examples in C#, we have demonstrated how the Command Pattern can be applied to real-world scenarios, providing a blueprint for creating systems that involve command execution, undo/redo functionality, or remote control of devices. Understanding and incorporating this pattern into your design practices can contribute to building modular, extensible, and maintainable software architectures, ensuring a clear separation between the sender and receiver of commands in your applications.