Fundamentals 10 min read

Understanding the Command Pattern with Python Examples

The article introduces the Command design pattern, explains its key roles such as Command, ConcreteCommand, Receiver, Invoker, and Client, outlines its advantages, and provides multiple Python code examples demonstrating light control, undo operations, file handling, shopping cart management, and menu actions.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Understanding the Command Pattern with Python Examples

The Command Pattern is a behavioral design pattern that encapsulates a request as an object, allowing parameterization of clients with different requests and decoupling the requester from the receiver.

Key Roles

Command : declares an interface with an execute method.

ConcreteCommand : implements the Command interface and defines the actual operation.

Receiver : contains the business logic that performs the action.

Invoker : holds a command and triggers its execution.

Client : creates concrete command objects and sets their receivers.

Advantages

Decouples invoker and receiver.

Improves extensibility – new commands can be added without changing existing code.

Supports undo/redo operations because each command encapsulates all information needed to reverse the action.

Facilitates request queuing, logging, and transactional behavior.

Example 1: Controlling a Light Switch

from abc import ABC, abstractmethod
# Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
# Concrete Command
class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light
    def execute(self):
        self.light.turn_on()
class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light
    def execute(self):
        self.light.turn_off()
# Receiver
class Light:
    def turn_on(self):
        print("Light is on.")
    def turn_off(self):
        print("Light is off.")
# Invoker
class RemoteControl:
    def __init__(self):
        self.commands = {}
    def set_command(self, slot, command):
        self.commands[slot] = command
    def press_button(self, slot):
        if slot in self.commands:
            self.commands[slot].execute()
# Client
light = Light()
light_on_command = LightOnCommand(light)
light_off_command = LightOffCommand(light)
remote_control = RemoteControl()
remote_control.set_command(1, light_on_command)
remote_control.set_command(2, light_off_command)
remote_control.press_button(1)  # Turns on the light
remote_control.press_button(2)  # Turns off the light

Example 2: Simple Undo Operation

from abc import ABC, abstractmethod
# Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
    @abstractmethod
    def undo(self):
        pass
# Concrete Command
class AddCommand(Command):
    def __init__(self, calculator, value):
        self.calculator = calculator
        self.value = value
    def execute(self):
        self.calculator.add(self.value)
    def undo(self):
        self.calculator.subtract(self.value)
# Receiver
class Calculator:
    def __init__(self):
        self.result = 0
    def add(self, value):
        self.result += value
        print(f"Result: {self.result}")
    def subtract(self, value):
        self.result -= value
        print(f"Result: {self.result}")
# Invoker
class Invoker:
    def __init__(self):
        self.commands = []
    def execute_command(self, command):
        command.execute()
        self.commands.append(command)
    def undo_last_command(self):
        if self.commands:
            last_command = self.commands.pop()
            last_command.undo()
# Client
calculator = Calculator()
add_command = AddCommand(calculator, 5)
invoker = Invoker()
invoker.execute_command(add_command)  # Result: 5
invoker.execute_command(AddCommand(calculator, 3))  # Result: 8
invoker.undo_last_command()  # Result: 5

Example 3: File Operations

from abc import ABC, abstractmethod
# Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
# Concrete Command
class CreateFileCommand(Command):
    def __init__(self, filename):
        self.filename = filename
    def execute(self):
        with open(self.filename, 'w') as f:
            print(f"Created file: {self.filename}")
class DeleteFileCommand(Command):
    def __init__(self, filename):
        self.filename = filename
    def execute(self):
        import os
        os.remove(self.filename)
        print(f"Deleted file: {self.filename}")
# Receiver
class FileHandler:
    def handle(self, command):
        command.execute()
# Client
file_handler = FileHandler()
create_file_command = CreateFileCommand("example.txt")
delete_file_command = DeleteFileCommand("example.txt")
file_handler.handle(create_file_command)  # Created file: example.txt
file_handler.handle(delete_file_command)  # Deleted file: example.txt

Example 4: Shopping Cart with Undo/Redo

from abc import ABC, abstractmethod
# Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
    @abstractmethod
    def undo(self):
        pass
# Concrete Command
class AddItemCommand(Command):
    def __init__(self, items, item):
        self.items = items
        self.item = item
    def execute(self):
        self.items.append(self.item)
        print(f"Added item: {self.item}")
    def undo(self):
        self.items.remove(self.item)
        print(f"Removed item: {self.item}")
# Receiver
class ShoppingCart:
    def __init__(self):
        self.items = []
    def display_items(self):
        print("Shopping Cart:")
        for item in self.items:
            print(item)
        print()
# Invoker
class Invoker:
    def __init__(self):
        self.commands = []
    def execute_command(self, command):
        command.execute()
        self.commands.append(command)
    def undo_last_command(self):
        if self.commands:
            last_command = self.commands.pop()
            last_command.undo()
# Client
shopping_cart = ShoppingCart()
add_item_command = AddItemCommand(shopping_cart.items, "Item 1")
invoker = Invoker()
invoker.execute_command(add_item_command)  # Added item: Item 1
invoker.execute_command(AddItemCommand(shopping_cart.items, "Item 2"))  # Added item: Item 2
shopping_cart.display_items()
invoker.undo_last_command()  # Removed item: Item 2
shopping_cart.display_items()

Example 5: Menu Operations

from abc import ABC, abstractmethod
# Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
# Concrete Command
class MenuItem1Command(Command):
    def execute(self):
        print("Executing Menu Item 1 command")
class MenuItem2Command(Command):
    def execute(self):
        print("Executing Menu Item 2 command")
# Receiver
class Menu:
    def display(self):
        print("Menu:")
        print("1. Menu Item 1")
        print("2. Menu Item 2")
        print()
# Invoker
class Invoker:
    def __init__(self):
        self.commands = {}
    def register_command(self, key, command):
        self.commands[key] = command
    def execute_command(self, key):
        if key in self.commands:
            self.commands[key].execute()
        else:
            print("Invalid command!")
# Client
menu = Menu()
menu_item1_command = MenuItem1Command()
menu_item2_command = MenuItem2Command()
invoker = Invoker()
invoker.register_command(1, menu_item1_command)
invoker.register_command(2, menu_item2_command)
menu.display()
invoker.execute_command(1)  # Executing Menu Item 1 command
invoker.execute_command(2)  # Executing Menu Item 2 command
invoker.execute_command(3)  # Invalid command!

These examples demonstrate how the Command pattern can be applied to various scenarios such as toggling devices, implementing undo/redo, performing file operations, managing a shopping cart, and handling menu actions, providing flexibility and extensibility in code design.

Design PatternsPythonsoftware designobject-orientedCommand Pattern
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.