undo機能を自分で作ろうとして、行き詰まったことはありませんか?
Ctrl+Z一回で済むおなじみの機能ですが、コードに落とし込むと意外に厄介です。ペイントソフト風の小さなトイプロジェクトでも、何日も悩むことがあります。
結論から言うと、undoの実装は大きく2通りです。
1つは実行した操作を逆に戻すCommandパターン、もう1つは操作前の状態全体を保存しておくMementoパターンです。
どちらも正解です。使う場面が違うだけです。今回はコードも交えて比較します。
Commandパターン — 操作をオブジェクトとしてラップする
Commandパターンでは、「何をしたか」を1つのオブジェクトにまとめます。
executeとundoを1組にしたオブジェクトだと考えてください。
たとえば文字を入力する操作なら、その反対である「入力した分だけ削除する」処理を同じオブジェクトにあらかじめ定義します。
protocol Command {
func execute()
func undo()
}
struct TypeCommand: Command {
let text: String
let editor: Editor
func execute() { editor.content += text }
func undo() { editor.content.removeLast(text.count) }
}
こうして作ったCommandオブジェクトを、スタックに順番に積みます。
Ctrl+Zが入力されたら、いちばん上を取り出してundo()を呼ぶだけです。
Commandパターンが保存するのは状態ではなく、「操作」です。
Mementoパターン — 状態をスナップショットとして保存する
Mementoはまったく異なるアプローチを取ります。
操作をどう戻すか考える代わりに、変更直前の状態全体をそのまま保存します。写真を撮るようなものです。
struct Memento { let content: String }
class Editor {
var content = ""
func save() -> Memento { Memento(content: content) }
func restore(_ m: Memento) { content = m.content }
}
let editor = Editor()
editor.content = "こんにちは"
let snapshot = editor.save() // ここでスナップショットを保存
editor.content = "こんにちは"
editor.restore(snapshot)
print(editor.content)
// 出力:こんにちは
undoが必要になったら、保存済みのスナップショットで状態を置き換えます。それだけです。
操作ごとに復元ロジックを書く必要がないので、気が楽です。
ただし、欠点も明確です。状態が大きいと、毎回全体をコピーするためメモリをかなり消費します。
では、どちらを使うべきか?
両方を使ってみて感じた違いを表にまとめました。
| 基準 | Commandパターン | Mementoパターン |
|---|---|---|
| 保存対象 | 操作(execute/undo) | 状態のスナップショット |
| メモリ | 軽い | 状態が大きいと重い |
| undoロジック | 操作ごとに実装 | 復元するだけ |
| redo | スタックを戻すだけで自然 | スナップショット配列で可能 |
| 向いている場所 | エディタ、グラフィックツール | ゲームセーブ、フォーム状態 |
一言でまとめると、こうです。
戻す操作を明確に定義できるならCommand、状態全体を保存するほうが楽ならMementoです。
いつ使い、いつ避けるか
実際に選ぶときは、次の基準を見ます。
- 操作単位が明確でredoも必要 → Command。代表例はエディタやドローイングツールです。
- 状態が小さく、スナップショットのコピーが負担にならない → Memento。フォーム入力や設定値の復元などです。
- 状態が巨大なのに変更は局所的 → Mementoは避けましょう。毎回全体をコピーするとメモリを圧迫します。Commandのほうが適しています。
- 戻すロジックがどうしても思いつかない → Commandを無理に使わず、Mementoでスナップショットを保存するのがすっきりします。
実務では、2つを組み合わせることもあります。操作はCommandで管理し、戻しにくい一部だけMementoでスナップショットを残す方法です。
面接ではこう聞かれます
Q. CommandパターンとMementoパターンの違いは何ですか?
Commandは操作をオブジェクトにカプセル化し、executeとundoを対にします。Mementoはオブジェクトの状態をスナップショットとして保存し、後から復元します。つまり、操作の逆演算で戻すか、状態を復元するかの違いです。
Q. undoスタックを実装するなら、どちらを選びますか?
操作単位が明確でredoも自然にサポートしたいならCommandを選びます。一方、状態が小さく、戻すロジックを書くのが面倒なら、Mementoでスナップショットを積むほうが単純で安全です。
2つのパターンは、ぜひ一度ずつ自分で実装してみてください。概念を読むだけのときと、スタックにオブジェクトが積まれ、Ctrl+Zで状態が戻る様子を目で見るときとでは、理解の深さがまったく違います。この記事が最初の一歩の助けになればうれしいです。

