Python Command Pattern

The Command Pattern is a behavioral design pattern that encapsulates a request as an object, thereby allowing you to parameterize clients with different requests. In simple terms,the Command Pattern wraps operations (such as opening files, saving data) into independent objects,allowing you to handle operations in the same way you handle data.

Analogy in Daily Life

Imagine the ordering process at a restaurant:

  • Customer(client) does not need to know how the cook prepares the dish
  • Waiter(invoker) receives the order but does not cook personally
  • Order(command object) contains the specific dish information
  • Cook(receiver) performs the actual cooking operations based on the order

This well-divided pattern is a real-life manifestation of the Command Pattern.


Why Need the Command Pattern?

Limitations of the Traditional Approach

In traditional programming, we usually directly call an object's methods:

Example

class Light:
    def turn_on(self):
        print("The light is on")
   
    def turn_off(self):
        print("The light is off")

# Direct call
light = Light()
light.turn_on()
light.turn_off()

This approach has several problems:

  1. Tight coupling: The caller needs to know the specific interface of the receiver
  2. Difficult to extend: Adding new features requires modifying existing code
  3. No support for undo/redo: Operations cannot be rolled back after execution

Advantages of the Command Pattern

The Command Pattern solves the above problems by introducing an intermediate layer (command object), providing:

  • Decoupling: No direct dependency between caller and receiver
  • Extensibility: Easy to add new commands
  • Support for undo/redo: Can record operation history
  • Queue support: Commands can be placed in a queue for delayed execution

Core Components of the Command Pattern

Basic Structure

The Command Pattern includes four core roles:

Role Responsibility Example
Command(Command Interface) Declares the interface for executing operations execute(), undo()
ConcreteCommand(Concrete Command) Implements the command interface and binds the receiver LightOnCommand, LightOffCommand
Receiver(Receiver) Knows how to perform operations Light, TV
Invoker(Invoker) Stores and executes commands RemoteControl

Complete Code Example

Basic Implementation

Let's understand the Command Pattern through a smart home lighting control example:

Example

from abc import ABC, abstractmethod
from typing import List

# 1. Command Interface
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
   
    @abstractmethod
    def undo(self):
        pass

# 2. Receiver - Light Device
class Light:
    def turn_on(self):
        print("💡 Light is on")
   
    def turn_off(self):
        print("💡 Light is off")

# 3. Concrete Command - Turn On Light Command
class LightOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_on()
   
    def undo(self):
        self.light.turn_off()

# 4. Concrete Command - Turn Off Light Command
class LightOffCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_off()
   
    def undo(self):
        self.light.turn_on()

# 5. Invoker - Remote Control
class RemoteControl:
    def __init__(self):
        self.command = None
        self.history: List[Command] = []
   
    def set_command(self, command: Command):
        self.command = command
   
    def press_button(self):
        if self.command:
            self.command.execute()
            self.history.append(self.command)
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Client code
if __name__ == "__main__":
    # Create device
    living_room_light = Light()
   
    # Create commands
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
   
    # Create remote control
    remote = RemoteControl()
   
    # Test turning on light
    print("=== Test turning on light ===")
    remote.set_command(light_on)
    remote.press_button()
   
    # Test turning off light
    print("\n=== Test turning off light ===")
    remote.set_command(light_off)
    remote.press_button()
   
    # Test undo
    print("\n=== Test undo operation ===")
    remote.press_undo()  # Undo turning off light, should turn on light
    remote.press_undo()  # Undo turning on light, should turn off light

Running result:


Advanced Application Example

Universal Remote Control Supporting Multiple Devices

Let's extend the previous example and create a universal remote control that supports multiple devices:

Example

# New device - TV
class TV:
    def __init__(self, location: str):
        self.location = location
        self.is_on = False
        self.volume = 50
   
    def turn_on(self):
        self.is_on = True
        print(f"📺 {self.location} TV is on")
   
    def turn_off(self):
        self.is_on = False
        print(f"📺 {self.location} TV is off")
   
    def set_volume(self, volume: int):
        self.volume = volume
        print(f"📺 {self.location} TV volume set to {volume}")

# TV-related commands
class TVOnCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.turn_on()
   
    def undo(self):
        self.tv.turn_off()
        self.tv.volume = self.previous_volume

class TVVolumeUpCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.set_volume(min(100, self.tv.volume + 10))
   
    def undo(self):
        self.tv.set_volume(self.previous_volume)

# Macro command - execute multiple commands with one press
class MacroCommand(Command):
    def __init__(self, commands: List[Command]):
        self.commands = commands
   
    def execute(self):
        for command in self.commands:
            command.execute()
   
    def undo(self):
        # Undo in reverse order
        for command in reversed(self.commands):
            command.undo()

# Improved remote control
class AdvancedRemoteControl:
    def __init__(self, slot_count: int = 4):
        self.on_commands: List[Command] = [None] * slot_count
        self.off_commands: List[Command] = [None] * slot_count
        self.history: List[Command] = []
   
    def set_command(self, slot: int, on_command: Command, off_command: Command):
        self.on_commands[slot] = on_command
        self.off_commands[slot] = off_command
   
    def press_on_button(self, slot: int):
        if self.on_commands[slot]:
            self.on_commands[slot].execute()
            self.history.append(self.on_commands[slot])
   
    def press_off_button(self, slot: int):
        if self.off_commands[slot]:
            self.off_commands[slot].execute()
            self.history.append(self.off_commands[slot])
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Test advanced remote control
def test_advanced_remote():
    print("=== Test universal remote control ===")
   
    # Create devices
    living_room_light = Light()
    bedroom_tv = TV("Bedroom")
   
    # Create commands
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
    tv_on = TVOnCommand(bedroom_tv)
    tv_volume_up = TVVolumeUpCommand(bedroom_tv)
   
    # Create macro command - cinema mode
    cinema_mode = MacroCommand([light_off, tv_on, tv_volume_up])
   
    # Set up remote control
    remote = AdvancedRemoteControl()
    remote.set_command(0, light_on, light_off)      # Slot 0: light control
    remote.set_command(1, tv_on, TVOnCommand(bedroom_tv))  # Slot 1: TV control
    remote.set_command(2, cinema_mode, light_on)    # Slot 2: cinema mode
   
    # Test various functions
    print("\n1. Turn on light:")
    remote.press_on_button(0)
   
    print("\n2. Enable cinema mode:")
    remote.press_on_button(2)
   
    print("\n3. Undo operation:")
    remote.press_undo()

if __name__ == "__main__":
    test_advanced_remote()

Parameters and Options of the Command Pattern

Extension of the Command Interface

In practical applications, the command interface can be extended with more functionality as needed:

Example

class AdvancedCommand(ABC):
    @abstractmethod
    def execute(self):
        """Execute command"""
        pass
   
    @abstractmethod
    def undo(self):
        """Undo command"""
        pass
   
    @abstractmethod
    def redo(self):
        """Redo command"""
        pass
   
    @abstractmethod
    def can_execute(self) -> bool:
        """Check whether the command can be executed"""
        pass
   
    @abstractmethod
    def get_description(self) -> str:
        """Get command description"""
        pass

Command Queue and Delayed Execution

The Command Pattern naturally supports command queues, which is very useful in many scenarios:

Example

class CommandQueue:
    def __init__(self):
        self.queue: List[Command] = []
   
    def add_command(self, command: Command):
        self.queue.append(command)
   
    def process_commands(self):
        while self.queue:
            command = self.queue.pop(0)
            if command.can_execute():
                command.execute()
   
    def clear(self):
        self.queue.clear()

Practical Exercises

Exercise 1: Implement an Air Conditioning Control System

Try to implement the Command Pattern for an air conditioning control system:

Example

# Your code here
class AirConditioner:
    def __init__(self):
        self.temperature = 26
        self.is_on = False
   
    def turn_on(self):
        # Implement turning on the air conditioner
        pass
   
    def turn_off(self):
        # Implement turning off the air conditioner
        pass
   
    def set_temperature(self, temp: int):
        # Implement setting the temperature
        pass

# Implement command classes related to the air conditioner
# AirConditionerOnCommand
# AirConditionerOffCommand  
# TemperatureUpCommand
# TemperatureDownCommand

Exercise 2: Implement Undo Functionality for a Text Editor

Create a simple text editor that supports text input and undo operations:

Example

class TextEditor:
    def __init__(self):
        self.content = ""
   
    def add_text(self, text: str):
        # Implement adding text
        pass
   
    def delete_text(self, length: int):
        # Implement deleting text
        pass

# Implement text editing commands
# AddTextCommand
# DeleteTextCommand

FAQ

Q: What scenarios is the Command Pattern suitable for?

A:The Command Pattern is especially suitable for the following scenarios:

  • When operations need to be parameterized (e.g., buttons bound to different functions)
  • When undo/redo functionality is needed
  • When operations need to be placed in a queue for delayed execution
  • When operation logs need to be recorded
  • When transaction operations need to be supported

Q: What are the disadvantages of the Command Pattern?

A:The main disadvantages include:

  • It may create a large number of command classes
  • It increases code complexity
  • It may be too cumbersome for simple operations

Q: How to choose whether to use the Command Pattern?

A:Consider the following points:

  • If you only need simple method calls, do not use the Command Pattern
  • If undo/redo functionality is needed, it is strongly recommended
  • If the system requires high decoupling, the Command Pattern is a good choice
  • If operations need queuing or logging, the Command Pattern is very suitable

Summary

The Command Pattern decouples the caller from the receiver by encapsulating operations as objects, providing powerful extensibility. Although it adds some code complexity, the Command Pattern is an indispensable design pattern in scenarios such as undo/redo, operation queues, and logging.

Key points:

  • The Command Pattern separates "what to do" from "who does it"
  • Supports advanced features such as undo, redo, and queues
  • Easy to extend with new commands
  • Widely used in GUI applications, transaction systems, and game development
Other extensions