Command Design Pattern


Here, we will see how to create Command Design Pattern with example.

Command Design Pattern

The Command Design Pattern is a behavioral pattern that turns requests or operations into objects. This enables you to parameterize methods with different requests, queue or log requests, and support undoable operations.

Example of Command Pattern

Let’s consider a home automation system where you can control devices like lights, fans, and TVs. We will explain in detail lator part of the article with code example.

Command Design Pattern

Advantages of the Command Pattern

  1. Decoupling the Invoker and Receiver - The command pattern decouples the object that invokes the operation (Invoker) from the object that knows how to perform the operation (Receiver).
  2. Easier to Add New Commands - Adding new commands is simple because you just need to add a new class implementing the Command interface without modifying existing code.
  3. Supports Undo/Redo Functionality - Since each command encapsulates all the information necessary to perform and reverse its action, the pattern easily supports undo/redo operations.
  4. Flexible Command Execution - Commands can be queued, logged, or executed in a specified order at a later time.
  5. Supports Macro Commands - You can compose multiple commands into a single command, enabling batch execution (i.e., macro commands).
  6. Reduces Coupling in Complex Systems - The pattern helps in reducing coupling in systems with complex interactions, allowing flexible and reusable designs.

When to Use the Command Pattern

  • When You Need Undo/Redo - Use this pattern when implementing operations that can be undone, like text editors, drawing applications, or file managers.
  • Logging Changes - When you need to log changes for future reference or auditing.
  • Macro Operations - When you need to group several operations into one.
  • Parameterizing Requests - If you need to queue, delay, or schedule the execution of commands, such as in job scheduling systems.
  • Callback Mechanisms - When you want to implement callback functionality in a more structured manner.

Problems in a System Without the Command Pattern

  1. Tight Coupling Between Invoker and Receiver - Without the command pattern, the Invoker (which triggers the operation) is tightly coupled with the Receiver (which performs the operation), making the system difficult to extend.
  2. Difficult to Implement Undo/Redo - Without encapsulating operations in command objects, adding undo/redo functionality becomes complicated.
  3. Lack of Flexibility for Extending Commands - Modifying or adding new commands often requires changes to the Invoker, leading to code maintenance issues.
  4. Code Duplication - Without the command pattern, there’s a tendency to duplicate logic across the system whenever similar commands are needed.

Example of Command Pattern

Let’s consider a home automation system where you can control devices like lights, fans, and TVs. Here, I’ll explain the key components and how the pattern works in this context.

Detailed Explanation

  1. Command Interface (ICommand) - This interface defines two methods: Execute() and Undo(). Any class implementing this interface will encapsulate an action (e.g., turning on or off a device) and its corresponding undo action.

  2. Receivers (Light, Fan, TV) - These are the classes that know how to perform the actual work. They are the devices that will be controlled by the commands. For instance, the Light class has methods to turn the light on and off.

  3. Concrete Command Classes

    • Each concrete command class implements the ICommand interface and holds a reference to one of the receiver objects.
    • For example, the TurnOnLightCommand class contains a reference to the Light object and calls its TurnOn() method when Execute() is invoked.
    • Similarly, it implements Undo() by calling the TurnOff() method, effectively reversing the action.
  4. Invoker (RemoteControl)

    • The invoker is the entity that triggers the command execution.
    • It holds a reference to a Command object and executes it when the PressButton() method is called.
    • It can also trigger the undo action using the PressUndo() method.
  5. Client Code

    • The client creates the necessary devices (Receivers), commands (Concrete Commands), and the remote control (Invoker).
    • It sets the appropriate command on the remote control and triggers the Execute() or Undo() operations.

C# Code Example

using System;
using System.Collections.Generic;

// Command Interface
public interface ICommand
{
    void Execute();
    void Undo();
}

// Light Class (Receiver)
public class Light
{
    public void TurnOn() => Console.WriteLine("Light is On");
    public void TurnOff() => Console.WriteLine("Light is Off");
}

// Fan Class (Receiver)
public class Fan
{
    public void TurnOn() => Console.WriteLine("Fan is On");
    public void TurnOff() => Console.WriteLine("Fan is Off");
}

// TV Class (Receiver)
public class TV
{
    public void TurnOn() => Console.WriteLine("TV is On");
    public void TurnOff() => Console.WriteLine("TV is Off");
}

// Concrete Commands for Light
public class TurnOnLightCommand : ICommand
{
    private Light _light;

    public TurnOnLightCommand(Light light)
    {
        _light = light;
    }

    public void Execute() => _light.TurnOn();
    public void Undo() => _light.TurnOff();
}

public class TurnOffLightCommand : ICommand
{
    private Light _light;

    public TurnOffLightCommand(Light light)
    {
        _light = light;
    }

    public void Execute() => _light.TurnOff();
    public void Undo() => _light.TurnOn();
}

// Concrete Commands for Fan
public class TurnOnFanCommand : ICommand
{
    private Fan _fan;

    public TurnOnFanCommand(Fan fan)
    {
        _fan = fan;
    }

    public void Execute() => _fan.TurnOn();
    public void Undo() => _fan.TurnOff();
}

public class TurnOffFanCommand : ICommand
{
    private Fan _fan;

    public TurnOffFanCommand(Fan fan)
    {
        _fan = fan;
    }

    public void Execute() => _fan.TurnOff();
    public void Undo() => _fan.TurnOn();
}

// Concrete Commands for TV
public class TurnOnTVCommand : ICommand
{
    private TV _tv;

    public TurnOnTVCommand(TV tv)
    {
        _tv = tv;
    }

    public void Execute() => _tv.TurnOn();
    public void Undo() => _tv.TurnOff();
}

public class TurnOffTVCommand : ICommand
{
    private TV _tv;

    public TurnOffTVCommand(TV tv)
    {
        _tv = tv;
    }

    public void Execute() => _tv.TurnOff();
    public void Undo() => _tv.TurnOn();
}

// Invoker (Remote Control)
public class RemoteControl
{
    private ICommand _command;

    public void SetCommand(ICommand command)
    {
        _command = command;
    }

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

    public void PressUndo()
    {
        _command.Undo();
    }
}

// Client Code
public class Client
{
    public static void Main(string[] args)
    {
        // Create Receivers
        Light light = new Light();
        Fan fan = new Fan();
        TV tv = new TV();

        // Create Concrete Commands for each device
        ICommand turnOnLight = new TurnOnLightCommand(light);
        ICommand turnOffLight = new TurnOffLightCommand(light);
        ICommand turnOnFan = new TurnOnFanCommand(fan);
        ICommand turnOffFan = new TurnOffFanCommand(fan);
        ICommand turnOnTV = new TurnOnTVCommand(tv);
        ICommand turnOffTV = new TurnOffTVCommand(tv);

        // Create Invoker (Remote Control)
        RemoteControl remote = new RemoteControl();

        // Turn On Light
        remote.SetCommand(turnOnLight);
        remote.PressButton();  // Output: Light is On
        remote.PressUndo();    // Output: Light is Off

        // Turn On Fan
        remote.SetCommand(turnOnFan);
        remote.PressButton();  // Output: Fan is On
        remote.PressUndo();    // Output: Fan is Off

        // Turn On TV
        remote.SetCommand(turnOnTV);
        remote.PressButton();  // Output: TV is On
        remote.PressUndo();    // Output: TV is Off
    }
}

How the Command Pattern Works in This Example

  • The remote control acts as the Invoker, issuing commands to various devices (the Receivers like Light, Fan, and TV).
  • The remote control does not know the specific details of how to turn on or off a light, fan, or TV. It just invokes the Execute() method of the command, which internally calls the appropriate method on the receiver.
  • The commands are encapsulated as objects, allowing the system to easily extend functionality, such as supporting undo, adding more devices, or scheduling commands.

Output

Command Design Pattern

Real-World Example - Text Editor

A text editor is a classic real-world example of the command pattern. Each text modification operation (like typing, deleting, or formatting) is encapsulated as a command object, which can be undone or redone. The text editor maintains a history of commands, allowing the user to undo or redo any number of previous actions.

The program has:

  1. Command objects for actions like typing, deleting, and formatting.
  2. A history stack to maintain commands for undo/redo.

C# Code

using System;
using System.Collections.Generic;

// Command interface
public interface ICommand
{
    void Execute();
    void UnExecute();
}

// Text Editor class that maintains text and supports actions
public class TextEditor
{
    public string Text { get; set; } = string.Empty;

    public void AddText(string text)
    {
        Text += text;
    }

    public void RemoveText(int length)
    {
        if (length <= Text.Length)
        {
            Text = Text.Substring(0, Text.Length - length);
        }
    }

    public override string ToString()
    {
        return Text;
    }
}

// Concrete command for adding text (Typing)
public class AddTextCommand : ICommand
{
    private readonly TextEditor _textEditor;
    private readonly string _textToAdd;

    public AddTextCommand(TextEditor textEditor, string textToAdd)
    {
        _textEditor = textEditor;
        _textToAdd = textToAdd;
    }

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

    public void UnExecute()
    {
        _textEditor.RemoveText(_textToAdd.Length);
    }
}

// Command Invoker which stores the command history for undo/redo
public class CommandInvoker
{
    private readonly Stack<ICommand> _undoCommands = new Stack<ICommand>();
    private readonly Stack<ICommand> _redoCommands = new Stack<ICommand>();

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _undoCommands.Push(command);
        _redoCommands.Clear(); // Clear redo history after a new command
    }

    public void Undo()
    {
        if (_undoCommands.Count > 0)
        {
            var command = _undoCommands.Pop();
            command.UnExecute();
            _redoCommands.Push(command);
        }
    }

    public void Redo()
    {
        if (_redoCommands.Count > 0)
        {
            var command = _redoCommands.Pop();
            command.Execute();
            _undoCommands.Push(command);
        }
    }
}

// Example of usage
public class Program
{
    public static void Main(string[] args)
    {
        var editor = new TextEditor();
        var invoker = new CommandInvoker();

        Console.WriteLine("Initial Text: " + editor);

        // Typing "Hello "
        invoker.ExecuteCommand(new AddTextCommand(editor, "Hello "));
        Console.WriteLine("After Typing: " + editor);

        // Typing "World!"
        invoker.ExecuteCommand(new AddTextCommand(editor, "World!"));
        Console.WriteLine("After Typing: " + editor);

        // Undo last typing (remove "World!")
        invoker.Undo();
        Console.WriteLine("After Undo: " + editor);

        // Redo last action (add "World!" again)
        invoker.Redo();
        Console.WriteLine("After Redo: " + editor);

        // Undo typing "World!" and "Hello "
        invoker.Undo();
        invoker.Undo();
        Console.WriteLine("After Undoing Twice: " + editor);

        // Redo one action (add "Hello " back)
        invoker.Redo();
        Console.WriteLine("After Redo Once: " + editor);
    }
}

Explanation

  1. Command Interface (ICommand) - Defines the Execute and UnExecute methods for executing and undoing commands.
  2. Text Editor (TextEditor) - Holds the current state of the text and has methods to modify the text (add and remove).
  3. Concrete Command (AddTextCommand) - Represents the "Add Text" action and implements the ICommand interface. It stores the text to be added, allowing undo by removing the same text.
  4. Command Invoker (CommandInvoker) - Manages the history of commands, allowing undo and redo operations.
  5. Program Class - Demonstrates usage of the text editor by typing, undoing, and redoing actions.

Output

Command Design Pattern

Applications that Use the Command Pattern

  1. GUI-based Applications: Such as buttons and menu actions in desktop applications (e.g., Microsoft Word, Photoshop), where each action is encapsulated as a command.
  2. Transaction Systems: For handling transactions, where each transaction is a command, and rollback functionality can be implemented.
  3. Game Development: In games, where actions (like character moves) can be encapsulated as commands and replayed for debugging or undoing moves.
  4. Job Scheduling Systems: Where each scheduled job is treated as a command that can be executed at a specific time.
  5. Remote Control Systems: Home automation systems controlling devices (e.g., lights, fans, air conditioners) based on user commands.

Prev Next

Top Articles

  1. What is JSON
  2. How to convert a javaScript object in JSON object
  3. Some Important JSON Examples
  4. Common JSON Interview Question