---
# System prepended metadata

title: 備忘錄模式 Memento
tags: [Behavioral Patterns]

---

---
tags: Behavioral Patterns
---

# 備忘錄模式 Memento

## 角色

**Originator** 就是定義中提到的要保留內部狀態的物件。現實例子就像是遊戲角色狀態，或是文書編輯器中的文字等。
**Memento** 保留 Originator 內部狀態 (資料) 的物件，例如遊戲中要存檔的資料。
**Caretaker** 主要功用是管理 Memento 物件。

![](https://i.imgur.com/YcMgtJt.png)


Originator 通常是遊戲的主體class，要有回傳當前狀態的Memento class的method以及接收 Memento的class用於恢復狀態

Memento 儲存數據用，比如生命跟經驗值

Caretaker 保管Memento的class，小範例可以只用單一變數複雜的可以用list儲存多個Memento

為什麼要有Caretaker，因為單一原則且提升內聚力(cohesion)


## 區隔 Carataker 以及 Originator
為什麼不把復原的動作寫到 Originator 就好呢？

Carataker 的工作是決定要在哪個時間點做快照、復原以及保留 Memento。

而 Originator 則是負責產生 Memento 以及利用傳遞過來的 Memento 復原自己的狀態。

Carataker 跟 Originator 有這樣的分配，好處是要做以下的修正時，可以不用去修改 Originator。
* 將復原動作修改為需要有一個步驟以上
* 除了復原之外，還要將目前的狀態儲存成檔案


## 範例

```java=


public class Memento {
    int money;                              // 手邊金錢總額
    Vector fruits;                          // 水果
    public int getMoney() {                 // 取得手邊金錢總額（narrow interface），表示可影響內部狀態的比例較少，外界能做的不多
        return money;
    }
    Memento(int money) {                    // 建構子（wide interface）
        this.money = money;
        this.fruits = new Vector();
    }
    void addFruit(String fruit) {           // 新增水果（wide interface）
        fruits.add(fruit);
    }
    Vector getFruits() {
         return (Vector)fruits.clone();
    }
}

//Originator
public class Gamer { 
    private int money;                          // 手邊金錢總額 
    private Vector fruits = new Vector();       // 水果 
    private Random random = new Random();       // 亂數產生器 
    private static String[] fruitsname = {      // 水果名稱一覽表 
        "蘋果", "葡萄", "香蕉", "橘子", 
    }; 
    public Gamer(int money) {                   // 建構子 
        this.money = money; 
    } 
    public int getMoney() {                     // 取得目前的手邊金錢總額 
        return money; 
    } 
    public void bet() {                         // 下注…開始遊戲 
        int dice = random.nextInt(6) + 1;       // 擲骰子 
        if (dice == 1) {                        // 第1次…手邊金錢有變多 
            money += 100; 
            System.out.println("手邊金錢增加了。"); 
        } else if (dice == 2) {                 // 第2次…手邊金錢剩一半 
            money /= 2; 
            System.out.println("手邊金錢剩一半。"); 
        } else if (dice == 6) {                 // 第6次…得到水果 
            String f = getFruit(); 
            System.out.println("得到水果(" + f + ")。"); 
            fruits.add(f); 
        } else {                                // 其他…沒有發生什麼事 
            System.out.println("沒有發生什麼事。"); 
        } 
    } 
    public Memento createMemento() {            // 快照存證（snapshot） 
        Memento m = new Memento(money); 
        Iterator it = fruits.iterator(); 
        while (it.hasNext()) { 
            String f = (String)it.next(); 
            if (f.startsWith("好吃的")) {      // 只儲存好吃的水果 
                m.addFruit(f); 
            } 
        } 
        return m; 
    } 
    public void restoreMemento(Memento memento) {   // 進行復原 
        this.money = memento.money; 
        this.fruits = memento.getFruits(); 
    } 
    public String toString() {                      // 輸出成字串 
        return "[money = " + money + ", fruits = " + fruits + "]"; 
    } 
    private String getFruit() {                     // 得到1個水果 
        String prefix = ""; 
        if (random.nextBoolean()) { 
            prefix = "好吃的"; 
        } 
        return prefix + fruitsname[random.nextInt(fruitsname.length)]; 
    } 
}


//直接把main當Caretaker
public class Main {
    public static void main(String[] args) {
        Gamer gamer = new Gamer(100);               // 一開始手邊金錢總額為100
        Memento memento = gamer.createMemento();    // 預先儲存最初狀態
        for (int i = 0; i < 100; i++) {
            System.out.println("==== " + i);        // 輸出次數
            System.out.println("現況:" + gamer);    // 輸出主人翁的目前狀態

            gamer.bet();    // 進行遊戲

            System.out.println("手邊金錢總額為" + gamer.getMoney() + "元。");

            // 決定如何處理Memento
            if (gamer.getMoney() > memento.getMoney()) {
                System.out.println("    （因為已經贏了不少，故先儲存目前狀態）");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
                System.out.println("    （因為已經輸了很多，故恢復到前次狀態）");
                gamer.restoreMemento(memento);
            }

            // 等候
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("");
        }
    }
}

```