BehavioralPythonverifiedVerified
Command Pattern in Python
Encapsulates a request as an object, allowing you to parameterize clients, queue or log requests, and support undoable operations.
How to Implement the Command Pattern in Python
1Step 1: Define the Command interface and receiver
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
class Counter:
def __init__(self) -> None:
self.value = 02Step 2: Implement concrete commands with undo support
class IncrementCommand(Command):
def __init__(self, counter: Counter, amount: int) -> None:
self._counter = counter
self._amount = amount
def execute(self) -> None:
self._counter.value += self._amount
def undo(self) -> None:
self._counter.value -= self._amount
class ResetCommand(Command):
def __init__(self, counter: Counter) -> None:
self._counter = counter
self._previous_value = 0
def execute(self) -> None:
self._previous_value = self._counter.value
self._counter.value = 0
def undo(self) -> None:
self._counter.value = self._previous_value3Step 3: Create a command history for execute and undo
class CommandHistory:
def __init__(self) -> None:
self._history: list[Command] = []
def execute(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo(self) -> None:
if self._history:
command = self._history.pop()
command.undo()4Step 4: Execute commands and undo them
counter = Counter()
history = CommandHistory()
history.execute(IncrementCommand(counter, 10)) # value = 10
history.execute(IncrementCommand(counter, 5)) # value = 15
history.execute(ResetCommand(counter)) # value = 0
history.undo() # value = 15
history.undo() # value = 10
print(counter.value) # 10"""Text Editor with Command pattern, undo/redo, and immutable state."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
# [step] Define editor state and command interface
@dataclass(frozen=True)
class EditorState:
content: str = ""
cursor_position: int = 0
selection_start: int = 0
selection_end: int = 0
class EditorCommand(ABC):
@property
@abstractmethod
def description(self) -> str: ...
@abstractmethod
def execute(self, state: EditorState) -> EditorState: ...
@abstractmethod
def undo(self, state: EditorState) -> EditorState: ...
# [step] Implement insert and delete commands
class InsertTextCommand(EditorCommand):
def __init__(self, text: str, position: int) -> None:
self._text = text
self._position = position
@property
def description(self) -> str:
return f'Insert "{self._text}" at {self._position}'
def execute(self, state: EditorState) -> EditorState:
content = (
state.content[: self._position]
+ self._text
+ state.content[self._position :]
)
return EditorState(
content=content,
cursor_position=self._position + len(self._text),
)
def undo(self, state: EditorState) -> EditorState:
content = (
state.content[: self._position]
+ state.content[self._position + len(self._text) :]
)
return EditorState(content=content, cursor_position=self._position)
class DeleteTextCommand(EditorCommand):
def __init__(self, start: int, end: int) -> None:
self._start = start
self._end = end
self._deleted_text = ""
@property
def description(self) -> str:
return f"Delete range [{self._start}, {self._end}]"
def execute(self, state: EditorState) -> EditorState:
self._deleted_text = state.content[self._start : self._end]
content = state.content[: self._start] + state.content[self._end :]
return EditorState(content=content, cursor_position=self._start)
def undo(self, state: EditorState) -> EditorState:
content = (
state.content[: self._start]
+ self._deleted_text
+ state.content[self._start :]
)
return EditorState(content=content, cursor_position=self._end)
# [step] Implement the TextEditor with undo/redo stacks
class TextEditor:
def __init__(self) -> None:
self._state = EditorState()
self._undo_stack: list[EditorCommand] = []
self._redo_stack: list[EditorCommand] = []
def execute(self, command: EditorCommand) -> None:
self._state = command.execute(self._state)
self._undo_stack.append(command)
self._redo_stack.clear()
def undo(self) -> str | None:
if not self._undo_stack:
return None
command = self._undo_stack.pop()
self._state = command.undo(self._state)
self._redo_stack.append(command)
return command.description
def redo(self) -> str | None:
if not self._redo_stack:
return None
command = self._redo_stack.pop()
self._state = command.execute(self._state)
self._undo_stack.append(command)
return command.description
@property
def content(self) -> str:
return self._state.content
@property
def history(self) -> list[str]:
return [c.description for c in self._undo_stack]Command Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Command Pattern in the Real World
“Think of a restaurant order ticket. A waiter (invoker) takes your order and writes it on a slip (command). The slip is handed to the kitchen (receiver) which executes it. The waiter doesn't cook anything—they just carry and deliver orders. Tickets can be queued, cancelled before cooking, or reviewed in an audit log at day's end.”