Explore the Command Pattern in Python with practical examples. Learn to encapsulate requests as objects and implement undo/redo functionality in a text editor.
The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all the information about the request. This transformation allows for parameterization of clients with queues, requests, and operations, as well as the support of undoable operations. In this section, we’ll delve into the implementation of the Command Pattern in Python, providing a detailed guide and practical examples to illustrate its use.
Before diving into the implementation, let’s understand the core components of the Command Pattern:
Let’s implement the Command Pattern in Python through a step-by-step guide.
The first step in implementing the Command Pattern is to define the Command interface. This interface will declare the execute
and undo
methods, which concrete commands must implement.
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
Concrete Commands implement the Command interface and define the relationship between the action and the receiver. Here, we’ll create two commands: WriteCommand
and DeleteCommand
.
class WriteCommand(Command):
def __init__(self, editor, content):
self.editor = editor
self.content = content
def execute(self):
self.editor.write(self.content)
def undo(self):
self.editor.delete(len(self.content))
class DeleteCommand(Command):
def __init__(self, editor, num_chars):
self.editor = editor
self.num_chars = num_chars
self.deleted_text = ""
def execute(self):
self.deleted_text = self.editor.text[-self.num_chars:]
self.editor.delete(self.num_chars)
def undo(self):
self.editor.write(self.deleted_text)
The Receiver class contains the business logic to perform the actions. In our example, the TextEditor
class acts as the receiver.
class TextEditor:
def __init__(self):
self.text = ""
def write(self, content):
self.text += content
def delete(self, num_chars):
self.text = self.text[:-num_chars]
def __str__(self):
return self.text
The Invoker class is responsible for executing commands. It can also maintain a history of commands for undo/redo functionality.
class CommandHistory:
def __init__(self):
self.history = []
self.redo_stack = []
def execute(self, command):
command.execute()
self.history.append(command)
self.redo_stack.clear()
def undo(self):
if self.history:
command = self.history.pop()
command.undo()
self.redo_stack.append(command)
def redo(self):
if self.redo_stack:
command = self.redo_stack.pop()
command.execute()
self.history.append(command)
The client code creates command objects, sets the receiver, and assigns commands to the invoker.
def main():
editor = TextEditor()
history = CommandHistory()
write_cmd1 = WriteCommand(editor, "Hello, ")
history.execute(write_cmd1)
print(editor)
write_cmd2 = WriteCommand(editor, "World!")
history.execute(write_cmd2)
print(editor)
# Undo last command
history.undo()
print("After undo:", editor)
# Redo last command
history.redo()
print("After redo:", editor)
# Delete last 6 characters
delete_cmd = DeleteCommand(editor, 6)
history.execute(delete_cmd)
print(editor)
# Undo delete
history.undo()
print("After undo delete:", editor)
if __name__ == "__main__":
main()
In this example, we implemented a simple text editor that supports writing and deleting text with undo and redo functionality. The TextEditor
class acts as the receiver, while WriteCommand
and DeleteCommand
encapsulate actions that can be executed or undone.
execute
and undo
.To better understand the flow of the Command Pattern, consider the following sequence diagram:
sequenceDiagram participant Client participant Invoker participant Command participant Receiver Client->>Command: Create command with Receiver Client->>Invoker: execute(Command) Invoker->>Command: execute() Command->>Receiver: action()
The Command Pattern is a powerful tool in software design, allowing for flexible and decoupled code. By encapsulating requests as objects, it enables easy implementation of undo/redo functionality and other complex operations. As you explore this pattern, consider how it can be applied to your projects to enhance modularity and maintainability.
By understanding and implementing the Command Pattern in Python, you can design systems that are more flexible, maintainable, and capable of handling complex command operations with ease.