C++ 設計模式 - 備忘錄模式 Memento Pattern

假如你正在玩一個遊戲,過了一個困難的關卡,突然間手滑點錯選項,整個進度被重置,你會不會希望有個「存檔」功能讓你可以回到那個關卡?這正是備忘錄模式 Memento Pattern 可以幫助我們解決的問題。備忘錄模式讓我們可以在需要時保存某個物件的狀態,並在必要時恢復到之前的狀態,像是一個系統的「後悔藥」。

什麼是備忘錄模式?

備忘錄模式是一種行為型設計模式,用來保存物件的狀態,這樣就可以在未來的某個時刻還原這些狀態。這個模式提供了一個方法讓物件能夠保存自己的狀態,並且在不違反封裝原則的情況下將其恢復。備忘錄模式可以想像成一個「快照」,讓我們可以回到某個特定的時間點,而不用擔心破壞系統的其他部分。

這個模式的基本角色包括:

  1. Originator(發起者):擁有狀態並可以建立或恢復備忘錄。
  2. Memento(備忘錄):保存Originator的內部狀態,並且只有Originator可以訪問它。
  3. Caretaker(照顧者):負責管理備忘錄,但不會修改其內容,只會在需要時請求Originator恢復狀態。

備忘錄模式在文字編輯器中的應用

一個很常見的應用例子就是文字編輯器中的「復原」與「重做」功能。當你編輯檔案時,每一次的修改都可以被記錄下來,這樣當你需要復原時,可以回到先前的狀態。讓我們用一個簡單的C++範例來解釋。

首先我們需要定義一個保存狀態的備忘錄類別,

1
2
3
4
5
6
7
8
9
// Memento 備忘錄介面
class Memento {
public:
Memento(const std::string& content) : content(content) {}
std::string getContent() const { return content; }

private:
std::string content;
};

接下來我們定義發起者類別(文字編輯器),這個類別會保存當前的狀態,並且能夠建立或恢復備忘錄,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Originator 發起者類別
class TextEditor {
public:
void appendText(const std::string& text) {
content += text;
}

void setText(const std::string& text) {
content = text;
}

std::string getText() const {
return content;
}

Memento save() const {
return Memento(content);
}

void restore(const Memento& memento) {
content = memento.getContent();
}

private:
std::string content;
};

照顧者會管理這些備忘錄,但它不會直接操作它們的內容,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 照顧者類別 History
class History {
public:
void push(const Memento& memento) {
mementos.push_back(memento);
}

Memento pop() {
if (mementos.empty()) {
return Memento(""); // 若無備忘錄可恢復,回傳空狀態
}
Memento lastMemento = mementos.back();
mementos.pop_back();
return lastMemento;
}

private:
std::vector<Memento> mementos;
};

在客戶端,讓我們看看如何使用這個文字編輯器系統,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main() {
TextEditor editor;
History history;

editor.appendText("123");
history.push(editor.save());
std::cout << "Current Text: " << editor.getText() << std::endl;

editor.appendText("456");
history.push(editor.save());
std::cout << "Current Text: " << editor.getText() << std::endl;

editor.appendText("789");
std::cout << "Current Text: " << editor.getText() << std::endl;

// 恢復到之前的狀態
editor.restore(history.pop());
std::cout << "After Undo: " << editor.getText() << std::endl;

editor.restore(history.pop());
std::cout << "After Undo: " << editor.getText() << std::endl;

return 0;
}

執行上述程式碼,我們會得到以下輸出:

1
2
3
4
5
Current Text: 123
Current Text: 123456
Current Text: 123456789
After Undo: 123456
After Undo: 123

這個範例示範了如何使用備忘錄模式來實現文字編輯器中的復原功能。發起者(文字編輯器)負責建立和恢復備忘錄,而照顧者則管理這些狀態。

備忘錄模式的優缺點

備忘錄模式最明顯的優點是它允許我們在不影響物件封裝的前提下保存和恢復狀態,這對於系統的靈活性和可維護性非常重要。舉例來說,文字編輯器的「復原」功能就是一個非常實用的情境,它可以讓我們安心編輯,隨時恢復先前狀態,減少操作錯誤的風險。

備忘錄模式也有的缺點,當系統需要保存的狀態非常龐大時,備忘錄會佔用大量的記憶體,進而影響效能。尤其在大型系統中,若狀態保存頻繁,可能會導致記憶體使用量過高。

總結

備忘錄模式特別適合應用於那些需要保存和恢復狀態的系統中,例如文字編輯器、遊戲或設定管理工具等。它讓我們可以在操作中放心地進行改變,隨時可以回到之前的狀態。然而我們也應該根據具體情況來決定是否使用,避免不必要的記憶體消耗。