3.3 - Command Pattern: Behavioral Design Patterns

In software development, the Command Pattern is a behavioral design pattern used to encapsulate a request as an object, thereby allowing us to parameterize clients with different requests, queue or log requests, and support undoable operations. It decouples the sender of a request from its receiver by introducing a layer of abstraction.

Let's walk through an example of how the Command Pattern works.

Components of the Command Pattern:

  1. Command Interface: Declares a method for executing a command.

  2. Concrete Command: Implements the command interface and defines the relationship between the receiver and an action.

  3. Receiver: Knows how to perform the operations associated with carrying out a request.

  4. Invoker: Asks the command to carry out the request.

  5. Client: The entity that creates and configures command objects.

Code Example

Below is an implementation of the Command Pattern with renamed functions and class names for clarity.

1. Command Interface

The Task interface declares a method for executing a command.

// Command Interface
interface Task {
    void execute();
}

2. Concrete Command

The ConcreteTask class implements the Task interface. It encapsulates a receiver object and calls its method when the command is executed.

// Concrete Command
class ConcreteTask implements Task {
    private Executor executor;

    public ConcreteTask(Executor executor) {
        this.executor = executor;
    }

    @Override
    public void execute() {
        executor.performOperation();
    }
}

3. Receiver

The Executor class contains the actual logic to perform the operation. In this case, it simply prints out a message indicating the action performed.

// Receiver
class Executor {
    public void performOperation() {
        System.out.println("Operation executed by the Executor");
    }
}

4. Invoker

The ActionInvoker class is responsible for executing commands. It doesn't know anything about the concrete command or receiver—it only knows about the Task interface.

// Invoker
class ActionInvoker {
    private Task task;

    public void setTask(Task task) {
        this.task = task;
    }

    public void executeTask() {
        task.execute();
    }
}

5. Client

The ClientApp class ties everything together. It creates the receiver (Executor), command (ConcreteTask), and invoker (ActionInvoker), and demonstrates how the command is executed.

// Client
public class ClientApp {
    public static void main(String[] args) {
        Executor executor = new Executor();         // Create the receiver
        Task task = new ConcreteTask(executor);     // Create the command and associate it with the receiver
        ActionInvoker invoker = new ActionInvoker(); // Create the invoker

        invoker.setTask(task);                     // Assign the command to the invoker
        invoker.executeTask();                     // Execute the command
    }
}

Explanation:

  1. Task Interface: Defines the execute() method, which is implemented by concrete tasks.

  2. ConcreteTask: Implements the Task interface and holds a reference to the receiver (Executor). When the execute() method is called, it triggers the appropriate action in the receiver.

  3. Executor: Contains the actual business logic that gets executed when a command is invoked.

  4. ActionInvoker: Holds a command and executes it without needing to know the details of the operation or the receiver.

  5. ClientApp: Sets up the interaction between the command, receiver, and invoker, and triggers the operation.

Benefits of the Command Pattern:

  1. Decoupling: The command pattern decouples the object that invokes the operation from the one that knows how to perform it.

  2. Parameterization: You can parameterize objects with operations, allowing dynamic behavior at runtime.

  3. Undo/Redo Operations: This pattern can be extended to support undoable operations by storing the previous state of a command.

  4. Extensibility: New commands can be added easily without modifying existing code.

Real-World Use Cases:

  • GUI Buttons: When buttons in a graphical user interface are clicked, they can be configured to perform different actions without the button needing to know the details of the action.

  • Transaction Systems: Where commands represent operations like debit and credit, which can be logged or undone.

  • Macro Commands: Multiple commands can be executed in sequence as part of a larger operation.

Conclusion:

The Command Pattern is a powerful design tool that helps you encapsulate requests as objects, allowing you to decouple the sender of a request from its receiver. It is ideal for scenarios where you need to issue requests to objects without knowing anything about the operations being requested or their receivers. This flexibility makes it a popular pattern in many real-world applications.