Command
The Command design pattern is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation allows you to parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations.
Key Concepts
- Command: An interface that declares a method for executing a command.
- Concrete Command: Implements the Command interface and defines the binding between a receiver object and an action.
- Receiver: The object that performs the actual action when the command’s execute method is called.
- Invoker: Asks the command to carry out the request.
- Client: Creates a ConcreteCommand object and sets its receiver.
Benefits
- Decoupling: Decouples the object that invokes the operation from the one that knows how to perform it.
- Extensibility: New commands can be added without changing existing code.
- Support for Undo/Redo: Commands can store state for undoable operations.
Example in Python
Let’s consider an example where we implement a simple text editor with commands for writing text, undoing the last operation, and redoing an undone operation.
from abc import ABC, abstractmethod
# Command Interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Concrete Command
class WriteCommand(Command):
def __init__(self, receiver, text):
self.receiver = receiver
self.text = text
def execute(self):
self.receiver.write(self.text)
def undo(self):
self.receiver.erase(self.text)
# Receiver
class TextEditor:
def __init__(self):
self.content = ""
def write(self, text):
self.content += text
def erase(self, text):
self.content = self.content[:-len(text)]
def __str__(self):
return self.content
# Invoker
class TextEditorInvoker:
def __init__(self):
self.history = []
self.undo_stack = []
def execute_command(self, command):
command.execute()
self.history.append(command)
self.undo_stack = []
def undo(self):
if self.history:
command = self.history.pop()
command.undo()
self.undo_stack.append(command)
def redo(self):
if self.undo_stack:
command = self.undo_stack.pop()
command.execute()
self.history.append(command)
# Client code
if __name__ == "__main__":
editor = TextEditor()
invoker = TextEditorInvoker()
command1 = WriteCommand(editor, "Hello ")
command2 = WriteCommand(editor, "World!")
invoker.execute_command(command1)
invoker.execute_command(command2)
print(f"After writing: {editor}")
invoker.undo()
print(f"After undo: {editor}")
invoker.redo()
print(f"After redo: {editor}")
Benefits in Practice
- Decoupling: The client code does not depend on the details of the command’s execution.
- Undo/Redo Support: The ability to undo and redo commands adds flexibility and control.
- Extensibility: New commands can be easily added without changing the invoker or the client code.
Another Example: Remote Control
Let’s consider another example where we implement a remote control for a home automation system.
from abc import ABC, abstractmethod
# Command Interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Concrete Commands
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.on()
def undo(self):
self.light.off()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.off()
def undo(self):
self.light.on()
class StereoOnCommand(Command):
def __init__(self, stereo):
self.stereo = stereo
def execute(self):
self.stereo.on()
def undo(self):
self.stereo.off()
class StereoOffCommand(Command):
def __init__(self, stereo):
self.stereo = stereo
def execute(self):
self.stereo.off()
def undo(self):
self.stereo.on()
# Receiver
class Light:
def on(self):
print("The light is on")
def off(self):
print("The light is off")
class Stereo:
def on(self):
print("The stereo is on")
def off(self):
print("The stereo is off")
# Invoker
class RemoteControl:
def __init__(self):
self.command_slots = {}
self.history = []
def set_command(self, slot, command):
self.command_slots[slot] = command
def press_button(self, slot):
command = self.command_slots.get(slot)
if command:
command.execute()
self.history.append(command)
def press_undo(self):
if self.history:
command = self.history.pop()
command.undo()
# Client code
if __name__ == "__main__":
remote = RemoteControl()
living_room_light = Light()
stereo = Stereo()
light_on = LightOnCommand(living_room_light)
light_off = LightOffCommand(living_room_light)
stereo_on = StereoOnCommand(stereo)
stereo_off = StereoOffCommand(stereo)
remote.set_command(1, light_on)
remote.set_command(2, light_off)
remote.set_command(3, stereo_on)
remote.set_command(4, stereo_off)
remote.press_button(1)
remote.press_button(3)
remote.press_undo()
remote.press_button(2)
remote.press_undo()
The Command design pattern is highly effective for encapsulating requests, decoupling invokers and receivers, supporting undo/redo functionality, and making the system more flexible and maintainable.