🧩 Command × Memento
✅ 組み合わせの意図
Command
により、操作(入力)をオブジェクトとして扱い、履歴として記録可能にするMemento
により、オブジェクトの状態を保存・復元できるようにする
この組み合わせにより、「何をしたか」だけでなく「どの状態だったか」も含めて記録・取り消しできる構造が実現する。
✅ よく使われるシーン
- テキストエディタや図形エディタなど、Undo/Redo 機能を提供する場面
- 履歴管理が必要なツール(設定変更・一括操作・バッチ処理など)
- UI 操作と内部状態を両方とも記録・巻き戻ししたいケース
✅ UML クラス図
✅ コード例
- 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,
✅ 解説
TextEditor
は文字列状態を持つ「受信者(Receiver)」で、状態の保存・復元機能(Memento
)も持つTypeCommand
はテキスト入力という操作をCommand
として表現し、実行前にMemento
で状態を保存CommandHistory
は操作の履歴を管理し、undo()
で巻き戻し可能
この構成により、操作履歴(Command)と状態復元(Memento)を 分離かつ連携 させた堅牢な履歴管理機能が実現できる。
✅ まとめ
Command
:操作を履歴として記録・再実行できるMemento
:状態をスナップショットとして保存・復元できる- 組み合わせにより「操作の記録」と「状態の保存・復元」が両立できる
- Undo/Redo 機能を備えたアプリ設計の基盤として有効