軟體設計

Command(命令)vs Memento(備忘錄):實作復原的兩種方法總整理

你是否曾在嘗試自行製作復原功能時卡住?

閱讀 4 分鐘
Command(命令)vs Memento(備忘錄):實作復原的兩種方法總整理 封面圖

你是否曾在嘗試自行製作復原功能時卡住?

這是按一下 Ctrl+Z 就結束的常見功能,但實際寫成程式碼後卻意外棘手。即使是類似小畫家的玩具專案,也可能讓你卡上好幾天。

先說結論:復原的實作大致分成兩種方式。

一種是把做過的操作反向還原的 Command 模式,另一種是完整儲存操作前狀態的 Memento 模式。

兩者都正確,只是適用時機不同。今天會搭配程式碼比較這兩種方式。


Command 模式 — 將操作封裝成物件

Command 模式會把「做了什麼」放進一個物件中。

可以把它想成將 execute 和 undo 綁在一起的物件。

例如有輸入文字的操作,就預先在同一個物件中定義其相反操作:「刪除輸入的文字」。

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)
// 輸出:嗨

需要復原時,將狀態替換成儲存好的快照即可。就這麼簡單。

不必為每個操作逐一撰寫還原邏輯,讓人輕鬆不少。

但缺點也很明顯。狀態很大時會相當耗用記憶體,因為每次都要完整複製。

以圖示呈現將 Command 物件堆疊起來的結構
以圖示呈現將 Command 物件堆疊起來的結構

所以該使用哪一種?

我實際使用兩者後感受到的差異,整理成表格如下。

基準 Command 模式 Memento 模式
儲存對象 操作(execute/undo) 狀態快照
記憶體 輕量 狀態大時較沉重
復原邏輯 針對每個操作自行實作 還原即可
redo 透過堆疊回退自然實作 可使用快照陣列實作
適合的場景 編輯器、繪圖工具 遊戲存檔、表單狀態

用一句話總結如下。

如果能清楚設計還原操作,就選 Command;如果完整保存狀態比較省心,就選 Memento。

曾經為了一個 Ctrl+Z 苦戰好幾天的那張桌子
曾經為了一個 Ctrl+Z 苦戰好幾天的那張桌子

何時使用,何時避開

實際選擇時,我會依照以下標準判斷。

  • 操作單位明確,且需要 redo → Command。編輯器或繪圖工具就是代表案例。
  • 狀態小,複製快照不會造成負擔 → Memento。適合表單輸入、還原設定值等場景。
  • 狀態龐大,但變更範圍很局部 → 請避開 Memento。每次完整複製都可能讓記憶體爆掉,Command 會更適合。
  • 完全想不到還原邏輯 → 不要勉強使用 Command,用 Memento 儲存快照會更乾淨。

實務上也會混用兩者:用 Command 管理操作,只針對難以還原的部分以 Memento 留下快照。


面試時會這樣問

Q. Command 模式和 Memento 模式有什麼差異?

Command 將「操作」封裝成物件,並讓 execute 與 undo 成對存在。Memento 則將物件的「狀態」儲存為快照,再進行還原。也就是說,差別在於透過操作的反向運算,還是透過狀態還原來復原。

Q. 如果要實作 undo 堆疊,你會選哪一種?

如果操作單位明確,且希望自然支援 redo,就選 Command。反之,若狀態很小,撰寫還原邏輯又很麻煩,使用 Memento 堆疊快照會更簡單安全。


建議你親自將兩種模式各實作一次。只閱讀概念,和親眼看到物件堆疊、狀態因 Ctrl+Z 還原,理解深度完全不同。希望今天的文章能幫助你踏出第一步。

延伸閱讀