1. The Problem
Problem: “I want to trigger actions without knowing who performs them or how.”
Real systems often need to execute operations decoupled from the object that performs them:
- UI Buttons triggering actions
- Job schedulers
- Undo / Redo systems
- Queues, event-driven systems
- Macro operations (run multiple commands as one)
Without abstraction, code ends up hard-wired:
if action == "turn_on_light": light.on()
elif action == "turn_off_light": light.off()
elif action == "increase_temp": ac.increase()
This leads to:
- Tight coupling between caller and receiver
- Hard-to-add new actions
- No clean way to queue, log, undo, or batch actions
- Violates Open/Closed Principle
We need a way to represent actions as objects.
2. The Command Pattern: Treat Actions as First-Class Objects
Command Pattern turns an operation call into a reusable object.
Core idea:
- Encapsulate a request (action) as an object
- Separate the caller (Invoker) from the doer (Receiver)
- Allow:
- Queuing
- Logging
- Undo
- Macro grouping
- Runtime composition
A Command system has:
- Command — object describing an action; exposes
execute() - Receiver — object that actually performs the work
- Invoker — triggers the command
- Client — wires everything
Benefits:
- No big conditional chains
- Runtime pluggability
- Uniform interface for all actions
- Supports undo, redo, scheduling, macros
Use Command when:
- You want to parameterize behavior
- You want queueable/loggable actions
- You need undo/redo
- You want macros
- You want UI buttons decoupled from actions
3. Implementation in Python
Command Interface
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
def undo(self):
pass
Example 1: Light Commands
Receiver
class Light:
def on(self):
print("Light is ON")
def off(self):
print("Light is OFF")
Concrete Commands
class LightOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.on()
def undo(self):
self.light.off()
class LightOffCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.off()
def undo(self):
self.light.on()
Example 2: AC Commands
Receiver
class AirConditioner:
def __init__(self):
self.temp = 24
def turn_on(self):
print("AC ON")
def turn_off(self):
print("AC OFF")
def increase_temp(self):
self.temp += 1
print(f"AC Temp Increased to {self.temp}")
def decrease_temp(self):
self.temp -= 1
print(f"AC Temp Decreased to {self.temp}")
Commands
class ACOnCommand(Command):
def __init__(self, ac: AirConditioner):
self.ac = ac
def execute(self):
self.ac.turn_on()
def undo(self):
self.ac.turn_off()
class IncreaseTempCommand(Command):
def __init__(self, ac: AirConditioner):
self.ac = ac
def execute(self):
self.ac.increase_temp()
def undo(self):
self.ac.decrease_temp()
Invoker
Invoker doesn’t know what action does. It only calls execute().
class RemoteControl:
def __init__(self):
self.history = []
def submit(self, command: Command):
command.execute()
self.history.append(command)
def undo_last(self):
if self.history:
last = self.history.pop()
last.undo()
Usage
light = Light()
ac = AirConditioner()
remote = RemoteControl()
remote.submit(LightOnCommand(light))
remote.submit(ACOnCommand(ac))
remote.submit(IncreaseTempCommand(ac))
remote.undo_last() # undo increase temperature
remote.undo_last() # undo AC on
4. Meta Command / Macro Command
A Meta Command (Macro Command) lets you bundle multiple commands as one.
Example: “Evening Mode”
- Turn on lights
- Turn on AC
- Increase temperature twice
class MacroCommand(Command):
def __init__(self, commands):
self.commands = commands
def execute(self):
for cmd in self.commands:
cmd.execute()
def undo(self):
# Reverse execution order
for cmd in reversed(self.commands):
cmd.undo()
Usage
macro = MacroCommand([
LightOnCommand(light),
ACOnCommand(ac),
IncreaseTempCommand(ac),
IncreaseTempCommand(ac),
])
remote.submit(macro)
remote.undo_last()
You now have a single command that represents a complex behavior.
5. When to Use Command (and When Not To)
You should use Command when:
- You need undo / redo
- You want macros
- You want to queue / schedule operations
- You want to decouple UI from business logic
- You want event-driven / job execution systems
You should not use Command when:
- You don’t need undo/macro/queueing
- Actions are trivial and won’t evolve
- Simple function references or callbacks are sufficient