Command Pattern
The Command is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request (receiver, method, arguments). This object can be queued, logged, undone, or executed later, decoupling the invoker (who triggers) from the receiver (who performs).
Key Principles
- Encapsulation: Wrap actions as objects.
- Decoupling: Invoker doesn't know receiver details.
- Extensibility: Support undo/redo, queuing, logging.
- Single Responsibility: Command handles execution logic.
Structure
- Command: Interface with
execute()(and optionalundo()). - Concrete Command: Implements command; holds receiver and parameters.
- Receiver: Actual object performing work.
- Invoker: Triggers command (e.g., button, menu).
- Client: Creates command and sets receiver.
Python Example: Text Editor with Undo
from abc import ABC, abstractmethod
# Receiver (performs actual work)
class TextEditor:
def __init__(self):
self.text = ""
self.cursor = 0
def insert(self, char):
self.text = self.text[:self.cursor] + char + self.text[self.cursor:]
self.cursor += 1
print(f"Inserted '{char}' → {self.text}")
def delete(self):
if self.cursor > 0:
self.text = self.text[:self.cursor-1] + self.text[self.cursor:]
self.cursor -= 1
print(f"Deleted → {self.text}")
def __str__(self):
return self.text
# Command Interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Concrete Commands
class InsertCommand(Command):
def __init__(self, editor: TextEditor, char: str):
self.editor = editor
self.char = char
def execute(self):
self.editor.insert(self.char)
def undo(self):
self.editor.delete()
class DeleteCommand(Command):
def __init__(self, editor: TextEditor):
self.editor = editor
def execute(self):
self.editor.delete()
def undo(self):
# Undo delete would need saved char – simplified here
print("Undo delete not implemented in simple example")
# Invoker (executes and manages history)
class EditorInvoker:
def __init__(self):
self.history = []
def execute_command(self, command: Command):
command.execute()
self.history.append(command)
def undo(self):
if self.history:
command = self.history.pop()
command.undo()
# Client Code
if __name__ == "__main__":
editor = TextEditor()
invoker = EditorInvoker()
invoker.execute_command(InsertCommand(editor, "H"))
invoker.execute_command(InsertCommand(editor, "e"))
invoker.execute_command(InsertCommand(editor, "l"))
invoker.execute_command(InsertCommand(editor, "l"))
invoker.execute_command(InsertCommand(editor, "o"))
print(f"Current text: {editor}")
invoker.undo() # Undo last 'o'
invoker.undo() # Undo last 'l'
print(f"After undo: {editor}")
Output:
Inserted 'H' → H
Inserted 'e' → He
Inserted 'l' → Hel
Inserted 'l' → Hell
Inserted 'o' → Hello
Current text: Hello
Deleted → Hell
Deleted → Hel
After undo: Hel
When to Use Command
- Undo/Redo: Editors, transactions.
- Queuing/Logging: Delayed execution, audit trails.
- Macro Commands: Compose multiple commands.
- Avoid: Simple direct calls.
Summary: Command encapsulates requests as objects, enabling queuing, undo, logging, and decoupling invoker from receiver.