Menu
Coddy logo textTech

Command Pattern

Part of the Object Oriented Programming section of Coddy's Python journey — lesson 49 of 64.

The Command Pattern encapsulates a request as an object, allowing you to queue operations, log requests, and support undo functionality. It separates the object that calls the operation from the one that performs it.

Here are simple command classes:

class Command:
    def execute(self):
        pass

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()

Each command encapsulates a specific operation on a receiver object.

Create the receiver that performs the actual work:

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

Create an invoker that executes commands:

class RemoteControl:
    def __init__(self):
        self.command = None
    
    def set_command(self, command):
        self.command = command
    
    def press_button(self):
        self.command.execute()

Use the command pattern:

# Create receiver
light = Light()

# Create commands
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# Create invoker
remote = RemoteControl()

# Execute different commands
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

Add support for undo operations:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

smart_remote = SmartRemote()
smart_remote.execute_command(LightOnCommand(light))
smart_remote.undo()  # Turns light off

Output:

Light is on
Light is off
Light is on
Light is off

Key Point: The Command Pattern turns requests into objects that can be stored, passed around, and executed later. The invoker doesn't need to know how to perform the operation - it just calls execute() on the command object. This enables features like undo/redo, queuing operations, and logging commands.

challenge icon

Challenge

Easy

In this challenge, you'll implement the foundational Command base class in command.py — the essential building block of the Command Pattern. This exercise focuses specifically on encapsulation: storing data privately and exposing it safely through a property.

Only modify command.py according to the TODO comments. The TODO comments will guide you to:

  • Store the command name as a private attribute (_name)
  • Expose it via a read-only property (name)
  • Implement a display_info() method that prints the command name

Note: This challenge covers the base Command class only — the Invoker, Receiver, and undo functionality are introduced in later lessons. Completing this step gives you the encapsulation foundation that the full pattern builds upon.

Your implementation will be tested by driver.py, which validates:

  • Basic functionality and output format
  • Edge cases (empty inputs, special characters, long names)
  • Read-only property protection (attempting obj.name = ... should raise AttributeError)
  • Presence of the private _name attribute

Cheat sheet

The Command Pattern encapsulates a request as an object, allowing you to queue operations, log requests, and support undo functionality.

Basic command structure:

class Command:
    def execute(self):
        pass

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()

Create a receiver that performs the actual work:

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

Create an invoker that executes commands:

class RemoteControl:
    def __init__(self):
        self.command = None
    
    def set_command(self, command):
        self.command = command
    
    def press_button(self):
        self.command.execute()

Usage example:

# Create receiver
light = Light()

# Create commands
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# Create invoker
remote = RemoteControl()

# Execute different commands
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

Add undo functionality:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

Try it yourself

# Import the Command class from command.py
from command import Command

# Comprehensive test case handler
test_case = input()

if test_case == "basic_test":
    obj = Command("Test Name")
    obj.display_info()
elif test_case == "validation_test":
    obj = Command("Validation Test")
    print(f"Name: {obj.name}")
elif test_case == "empty_name_test":
    obj = Command("")
    obj.display_info()
    print(f"Empty name handled: {'Yes' if obj.name == '' else 'No'}")
elif test_case == "property_access_test":
    obj = Command("Property Test")
    original_name = obj.name
    try:
        # This should fail since name is a read-only property
        obj.name = "Modified Name"
        print("Property protection failed")
    except AttributeError:
        print("Property protected successfully")
    print(f"Name unchanged: {obj.name == original_name}")
elif test_case == "multiple_commands_test":
    commands = [
        Command("First Command"),
        Command("Second Command"),
        Command("Third Command")
    ]
    for cmd in commands:
        cmd.display_info()
elif test_case == "attribute_test":
    obj = Command("Attribute Test")
    has_private_name = hasattr(obj, "_name")
    print(f"Has _name attribute: {has_private_name}")
elif test_case == "special_chars_test":
    obj = Command("!@#$%^&*()_+{}[]|\\:;\"'<>,.?/")
    obj.display_info()
elif test_case == "long_name_test":
    long_name = "A" * 100
    obj = Command(long_name)
    obj.display_info()
    print(f"Name length: {len(obj.name)}")
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming