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.
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 lightExample 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: 5Example 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.txtExample 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.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.