Skip to Content
All posts

The Command Pattern in C#/.Net

3 min read ·  — #design-patterns#structrural-patterns#command

The Command Pattern in C#/.Net

Introduction

Design patterns serve as the blueprint for solving common software design issues. Among these, Behavioral Design Patterns stand out by focusing on how objects interact and distribute responsibilities. One gem in this category is the Command pattern. Its beauty lies not just in solving problems but in the elegance and flexibility it brings to software design, especially in complex systems. This pattern turns a request into a stand-alone object that contains all information about the request. This transformation allows for parameterizing methods with different requests, delaying or queuing a request's execution, and supporting undoable operations. It's particularly compelling in the realms of GUI command processing, transactional operations, and logging activities.

The essence of the Command pattern in C#/.Net environments is to encapsulate a request as an object, thereby allowing developers to parameterize clients with queues, requests, and operations. Through encapsulation, commands can be manipulated and extended like any other object, offering a flexible and scalable solution for complex command processing needs.

Real-World Scenario: Implementing Undo Functionality in a Text Editor

Consider a text editor application. One of the features users expect is the ability to undo their last actions, whether it's typing text, deleting text, or formatting. Implementing such a feature can quickly become cumbersome without a systematic approach. Here's where the Command pattern shines.

Step 1: Define the Command Interface

First, we define a command interface with an Execute() method that all concrete commands will implement. This interface acts as a contract, ensuring consistency and predictability for command execution.

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

Step 2: Create Concrete Commands

Next, we create concrete command classes for each action that can be performed in the text editor, such as adding text, deleting text, or changing text formatting. Each command class implements the ICommand interface.

public class AddTextCommand : ICommand
{
    private readonly Editor _editor;
    private string _textToAdd;

    public AddTextCommand(Editor editor, string text)
    {
        _editor = editor;
        _textToAdd = text;
    }

    public void Execute()
    {
        _editor.AddText(_textToAdd);
    }

    public void Undo()
    {
        _editor.RemoveText(_textToAdd.Length);
    }
}

Step 3: The Invoker

The invoker class is responsible for invoking commands. It holds a command and at the right moment, calls the command's Execute() method. The invoker can also store a history of commands to support undo operations.

public class CommandManager
{
    private readonly Stack<ICommand> _commands = new Stack<ICommand>();

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

    public void Undo()
    {
        if (_commands.Count > 0)
        {
            var command = _commands.Pop();
            command.Undo();
        }
    }
}

Step 4: The Client

Finally, the client creates a concrete command instance and passes it to the invoker to execute.

public class Editor
{
    public void AddText(string text) { /* Implementation */ }
    public void RemoveText(int length) { /* Implementation */ }
    // Other text manipulation methods
}

class Program
{
    static void Main(string[] args)
    {
        var editor = new Editor();
        var manager = new CommandManager();

        var addTextCommand = new AddTextCommand(editor, "Hello, World!");
        manager.ExecuteCommand(addTextCommand);

        // Perform undo
        manager.Undo();
    }
}

This example demonstrates how the Command pattern can elegantly encapsulate and execute operations, providing a robust and flexible mechanism for actions like undo. It separates the concerns of command definition, execution, and invocation, leading to cleaner, more maintainable code. In the context of a text editor, it not only simplifies the implementation of undo functionality but also paves the way for future extensions such as redo or macro recording.

The Command pattern's utility extends beyond text editors, finding relevance in various applications such as GUI button and menu action handling, transaction systems where operations need to be reversible, and job queues where commands can be dispatched and executed asynchronously.

By mastering the Command pattern, developers can significantly enhance their toolkit for tackling complex scenarios in C#/.Net applications, making their software designs more robust, flexible, and maintainable.