Forem

Cover image for Design Patterns #3: Power of Command.
Serhii Korol
Serhii Korol

Posted on

Design Patterns #3: Power of Command.

Today, we’ll dive into another behavioral design pattern: the Command Pattern. I’ll explain what it is, demonstrate its implementation with a simple example, and discuss its impact on performance. By the end of this article, you’ll understand why this pattern is a powerful tool for building flexible and extensible systems.

What is the Command Pattern?

The Command Pattern is a behavioral design pattern that encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. The core idea is to decouple the object that invokes an operation from the one that knows how to perform it.

The pattern consists of the following key components:

  1. Client: Creates and configures the concrete command objects.
  2. Invoker: Holds a command and triggers its execution.
  3. Receiver: Knows how to perform the actual action.
  4. Command: An interface or abstract class that defines the execution method.
  5. Concrete Command: Implements the Command interface and binds the action to the receiver.

scheme

Implementing the Command Pattern

Let’s explore the Command Pattern with a practical example: a light remote control. In this scenario, we’ll create commands to turn a light on and off, and we’ll also implement an undo feature.

Step 1: Define the Command Interface

We start by defining an interface ICommand with two methods: Execute() and Undo().

public interface ICommand
{
    void Execute();
    void Undo();
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Concrete Commands

Next, we implement two concrete commands: LightOnCommand and LightOffCommand. These commands encapsulate the actions of turning the light on and off.

public class LightOnCommand(Light light) : ICommand
{
    public void Execute()
    {
        light.TurnOn();
    }

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

public class LightOffCommand(Light light) : ICommand
{
    public void Execute()
    {
        light.TurnOff();
    }

    public void Undo()
    {
        light.TurnOn();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement the Receiver

The Light class acts as the receiver, which knows how to perform the actual actions.

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

    public void TurnOff()
    {
        Console.WriteLine("The light is OFF");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Invoker

The CommandRemoteControl class acts as the invoker. It holds a command and triggers its execution when needed.

public class CommandRemoteControl
{
    private ICommand? _command;

    public void Invoke(ICommand? command)
    {
        _command = command;
    }

    public void PressButton()
    {
        if (_command != null)
        {
            _command.Execute();
        }
        else
        {
            Console.WriteLine("No command set.");
        }
    }

    public void PressUndo()
    {
        if (_command != null)
        {
            _command.Undo();
        }
        else
        {
            Console.WriteLine("No command set to undo.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Simulate User Interaction

To demonstrate how the pattern works, we’ll simulate user interaction with a method that calls different commands based on user choices.

    public static void RunCommand()
    {
        Light light = new Light();
        CommandRemoteControl commandRemote = new CommandRemoteControl();
        ICommand lightOnCommand = new LightOnCommand(light);
        ICommand lightOffCommand = new LightOffCommand(light);
        ICommand? lastCommand = null;
        int[] choices = { 1, 2, 3, 0 };
        Console.WriteLine("Choose light action: 1. On, 2. Off, 3. Undo");
        foreach (var choice in choices)
        {
            switch (choice)
            {
                case 1:
                    Console.WriteLine($"Chosen action: On");
                    commandRemote.Invoke(lightOnCommand);
                    lastCommand = lightOnCommand;
                    commandRemote.PressButton();
                    break;
                case 2:
                    Console.WriteLine($"Chosen action: Off");
                    commandRemote.Invoke(lightOffCommand);
                    lastCommand = lightOffCommand;
                    commandRemote.PressButton();
                    break;
                case 3:
                    Console.WriteLine($"Chosen action: Undo");
                    commandRemote.PressUndo();
                    commandRemote.Invoke(lastCommand);
                    break;
                default:
                    Console.WriteLine("Chosen action: Unknown");
                    commandRemote.Invoke(null);
                    commandRemote.PressButton();
                    break;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

When you run this code, you’ll see the following output:

command result

I needed four classes to implement this pattern. You might need more classes if you have something more complicated, like a light switcher with many commands. This will increase the complexity of the entire application.

Breaking the Rules: A Simplified Approach

While the Command Pattern provides flexibility, it’s tempting to simplify the code by breaking the pattern. Let’s explore a non-command approach where we directly pass commands as integers.

public class NonCommandRemoteControl(Light? light)
{
    public void PressButton(int command)
    {
        switch (command)
        {
            case 1:
                light?.TurnOn();
                break;
            case 2:
                light?.TurnOff();
                break;
            default:
                Console.WriteLine("Incorrect Command.");
                break;
        }
    }

    public void PressUndo(int lastCommand)
    {
        switch (lastCommand)
        {
            case 1:
                light?.TurnOff();
                break;
            case 2:
                light?.TurnOn();
                break;
            default:
                Console.WriteLine("No previous action to undo.");
                break;
        }
    }
Enter fullscreen mode Exit fullscreen mode

To simulate user interaction, we use a similar method:

public static void RunNonCommand()
    {
        Light light = new Light();
        NonCommandRemoteControl nonCommandRemote = new NonCommandRemoteControl(light);

        int lastCommand = 0;
        int[] choices = { 1, 2, 3, 0 };

        Console.WriteLine("Choose light action: 1. On, 2. Off, 3. Undo");
        foreach (var choice in choices)
        {
            switch (choice)
            {
                case 1:
                    Console.WriteLine($"Chosen action: On");
                    lastCommand = choice;
                    nonCommandRemote.PressButton(choice);
                    break;
                case 2:
                    Console.WriteLine($"Chosen action: Off");
                    lastCommand = choice;
                    nonCommandRemote.PressButton(choice);
                    break;
                case 3:
                    Console.WriteLine($"Chosen action: Undo");
                    nonCommandRemote.PressUndo(lastCommand);
                    break;
                default:
                    Console.WriteLine("Chosen action: Unknown");
                    nonCommandRemote.PressButton(0);
                    break;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

The output is identical to the Command Pattern implementation:

non command result

Performance Comparison

You might wonder why we should use the Command Pattern when the simplified approach achieves the same result with less code. A benchmark reveals that both approaches have similar performance, with the Command Pattern being slightly faster despite its larger codebase.

benchmark

Why Use the Command Pattern?

While the simplified approach works for simple scenarios, the Command Pattern shines in more complex applications. Here’s why:

  1. Flexibility: You can easily add new commands without modifying existing code.
  2. Extensibility: Commands can be queued, logged, or executed in sequence.
  3. Decoupling: The invoker is decoupled from the receiver, promoting lower coupling and higher cohesion.
  4. Undo/Redo Support: The pattern naturally supports undoable operations.

Conclusion

The Command Pattern is a powerful tool for building flexible and maintainable systems. While it may seem like overkill for simple use cases, its benefits become evident as your application grows in complexity. By encapsulating actions as objects, you gain the ability to manage, extend, and reuse commands effortlessly.

You can find the source code by clicking the link.

I hope this article has been helpful! If you have any questions or feedback, feel free to leave a comment. Happy coding!

Buy Me A Beer

Top comments (1)

Collapse
 
stevsharp profile image
Spyros Ponaris

Thanks for sharing! I’ve implemented something similar using Blazor and the Command Pattern.
You can check out the code here:
github.com/stevsharp/BlazorAndComm...
I hope you find it interesting!