Understanding the Command Pattern

The Command Pattern consists of four main components:

  1. Command: An interface for executing an operation.
  2. ConcreteCommand: Implements the Command interface and defines the binding between a receiver and an action.
  3. Receiver: Knows how to perform the operations associated with carrying out a request.
  4. Invoker: Asks the command to execute the request.

Benefits of the Command Pattern

  • Decoupling: The sender and receiver are decoupled, promoting low coupling and high cohesion.
  • Undo/Redo Functionality: Commands can be stored and executed later, enabling undo and redo operations.
  • Queue Requests: Commands can be queued and executed at a later time.

Implementing the Command Pattern in Python

Let’s implement the Command Pattern step-by-step. We will create a simple text editor where we can execute commands like adding text and removing text.

Step 1: Define the Command Interface

We start by defining a base command interface. In Python, this can be done using an abstract base class.

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

    @abstractmethod
    def undo(self):
        pass

Step 2: Create Concrete Command Classes

Next, we will create concrete commands for adding and removing text.

class AddTextCommand(Command):
    def __init__(self, editor, text):
        self.editor = editor
        self.text = text

    def execute(self):
        self.editor.add_text(self.text)

    def undo(self):
        self.editor.remove_text(len(self.text))

class RemoveTextCommand(Command):
    def __init__(self, editor, length):
        self.editor = editor
        self.length = length

    def execute(self):
        self.editor.remove_text(self.length)

    def undo(self):
        self.editor.add_text(self.editor.last_removed_text)

Step 3: Create the Receiver

The receiver is responsible for the actual operations. In our case, it will be the text editor.

class TextEditor:
    def __init__(self):
        self.content = ""
        self.last_removed_text = ""

    def add_text(self, text):
        self.content += text
        print(f"Added text: '{text}' | Current content: '{self.content}'")

    def remove_text(self, length):
        if length > len(self.content):
            length = len(self.content)
        self.last_removed_text = self.content[-length:]
        self.content = self.content[:-length]
        print(f"Removed last {length} characters | Current content: '{self.content}'")

Step 4: Implement the Invoker

The invoker will keep track of the commands and execute them.

class CommandInvoker:
    def __init__(self):
        self.history = []

    def execute_command(self, command):
        command.execute()
        self.history.append(command)

    def undo(self):
        if self.history:
            command = self.history.pop()
            command.undo()

Step 5: Putting It All Together

Now we can use the Command Pattern in our text editor application.

if __name__ == "__main__":
    editor = TextEditor()
    invoker = CommandInvoker()

    # Adding text
    add_command = AddTextCommand(editor, "Hello, World!")
    invoker.execute_command(add_command)

    # Removing text
    remove_command = RemoveTextCommand(editor, 6)
    invoker.execute_command(remove_command)

    # Undoing the last command
    invoker.undo()  # Should add back the last removed text

Output

When you run the above code, the output will be:

Added text: 'Hello, World!' | Current content: 'Hello, World!'
Removed last 6 characters | Current content: 'Hello, '
Added text: 'World!' | Current content: 'Hello, World!'

Summary

The Command Pattern is a powerful design pattern that can help you create more maintainable and decoupled code. By encapsulating requests as objects, you can easily manage the execution of commands, implement undo/redo functionality, and queue operations for later execution.

Comparison of Command Pattern Components

ComponentDescription
CommandInterface for executing operations
ConcreteCommandImplements the Command interface
ReceiverPerforms the actual operations
InvokerInvokes the command and maintains command history

Learn more with useful resources