π§© Command Γ Memento
β Combined Intentβ
- Use the
Command
pattern to encapsulate operations (like input) as objects and enable history tracking. - Use the
Memento
pattern to capture and restore the internal state of objects.
This combination enables a structure where you can track what was done and also what the system's state was at that time β allowing both command replay and full state rollback.
β Common Use Casesβ
- Applications like text editors or drawing tools where Undo/Redo is essential.
- Tools that require change tracking (e.g., batch updates, system configurations).
- When both user actions and internal states need to be recorded and reverted.
β UML Class Diagramβ
β Code Exampleβ
- TypeScript
- PHP
- Python
// Receiver
class TextEditor {
private content: string = "";
append(text: string) {
this.content += text;
}
deleteLast(n: number) {
this.content = this.content.slice(0, -n);
}
getContent() {
return this.content;
}
save(): EditorMemento {
return new EditorMemento(this.content);
}
restore(memento: EditorMemento) {
this.content = memento.getState();
}
}
// Memento
class EditorMemento {
constructor(private readonly state: string) {}
getState(): string {
return this.state;
}
}
// Command γ€γ³γΏγΌγγ§γΌγΉ
interface Command {
execute(): void;
undo(): void;
}
// Concrete Command
class TypeCommand implements Command {
private backup: EditorMemento;
constructor(private editor: TextEditor, private text: string) {}
execute(): void {
this.backup = this.editor.save();
this.editor.append(this.text);
}
undo(): void {
this.editor.restore(this.backup);
}
}
// Invoker
class CommandHistory {
private history: Command[] = [];
executeCommand(command: Command) {
command.execute();
this.history.push(command);
}
undo() {
const command = this.history.pop();
if (command) command.undo();
}
}
// δ½Ώη¨δΎ
const editor = new TextEditor();
const history = new CommandHistory();
history.executeCommand(new TypeCommand(editor, "Hello, "));
history.executeCommand(new TypeCommand(editor, "world!"));
console.log(editor.getContent()); // Hello, world!
history.undo();
console.log(editor.getContent()); // Hello,
<?php
// Receiver
class TextEditor {
private string $content = "";
public function append(string $text): void {
$this->content .= $text;
}
public function deleteLast(int $n): void {
$this->content = substr($this->content, 0, -$n);
}
public function getContent(): string {
return $this->content;
}
public function save(): EditorMemento {
return new EditorMemento($this->content);
}
public function restore(EditorMemento $memento): void {
$this->content = $memento->getState();
}
}
// Memento
class EditorMemento {
public function __construct(private string $state) {}
public function getState(): string {
return $this->state;
}
}
// Command Interface
interface Command {
public function execute(): void;
public function undo(): void;
}
// Concrete Command
class TypeCommand implements Command {
private EditorMemento $backup;
public function __construct(
private TextEditor $editor,
private string $text
) {}
public function execute(): void {
$this->backup = $this->editor->save();
$this->editor->append($this->text);
}
public function undo(): void {
$this->editor->restore($this->backup);
}
}
// Invoker
class CommandHistory {
private array $history = [];
public function executeCommand(Command $command): void {
$command->execute();
$this->history[] = $command;
}
public function undo(): void {
$command = array_pop($this->history);
if ($command) {
$command->undo();
}
}
}
// δ½Ώη¨δΎ
$editor = new TextEditor();
$history = new CommandHistory();
$history->executeCommand(new TypeCommand($editor, "Hello, "));
$history->executeCommand(new TypeCommand($editor, "world!"));
echo $editor->getContent(); // Hello, world!
$history->undo();
echo $editor->getContent(); // Hello,
# Receiver
class TextEditor:
def __init__(self):
self._content = ""
def append(self, text: str):
self._content += text
def delete_last(self, n: int):
self._content = self._content[:-n]
def get_content(self) -> str:
return self._content
def save(self):
return EditorMemento(self._content)
def restore(self, memento):
self._content = memento.get_state()
# Memento
class EditorMemento:
def __init__(self, state: str):
self._state = state
def get_state(self) -> str:
return self._state
# Command Interface
class Command:
def execute(self):
pass
def undo(self):
pass
# Concrete Command
class TypeCommand(Command):
def __init__(self, editor: TextEditor, text: str):
self.editor = editor
self.text = text
self.backup = None
def execute(self):
self.backup = self.editor.save()
self.editor.append(self.text)
def undo(self):
if self.backup:
self.editor.restore(self.backup)
# Invoker
class CommandHistory:
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()
# δ½Ώη¨δΎ
editor = TextEditor()
history = CommandHistory()
history.execute_command(TypeCommand(editor, "Hello, "))
history.execute_command(TypeCommand(editor, "world!"))
print(editor.get_content()) # Hello, world!
history.undo()
print(editor.get_content()) # Hello,
β Explanationβ
TextEditor
holds the current text and acts as the Receiver, also implementing state snapshot functionality (Memento
).TypeCommand
represents a text input operation as aCommand
, and saves a snapshot before execution.CommandHistory
stores the list of commands and providesundo()
functionality.
By combining these patterns, we can decouple input behavior and state recovery, allowing for a clean and maintainable Undo/Redo system.
β Summaryβ
- Command: Encapsulates operations and enables command history.
- Memento: Captures and restores object state.
- Their combination allows tracking of both actions and states.
- Ideal for implementing undoable workflows and reliable state transitions.