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 offOutput:
Light is on
Light is off
Light is on
Light is offKey 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
EasyIn 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 raiseAttributeError) - Presence of the private
_nameattribute
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)}")This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self ParameterMethodsAttributesConstructor Method (__init__)Recap - Simple Calculator4Inheritance
Basic InheritanceThe super() FunctionMethod OverridingMultiple InheritanceMethod Resolution OrderRecap - Employee Hierarchy7Special Methods
Magic Methods IntroductionOperator OverloadingContainer Magic MethodsRecap - Custom List10Design Patterns Part 1
Intro to design patternSingleton PatternFactory PatternObserver PatternStrategy Pattern2Decorators
Introduction to DecoratorsProperty DecoratorStatic Method DecoratorClass Method Decorator5Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceMixinsStatic and Class MethodsClass DecoratorsContext Managers11Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite Pattern3Class Properties
Instance vs Class VariablesProperty DecoratorsPrivate AttributesRecap - Bank Account Manager6Encapsulation
Public, Protected, Private MemAccess ModifiersInformation HidingProperty Decorators AdvancedRecap - Student Records System12Project: Library Management
Project OverviewBook and User Classes