Memento 模式

Memento 模式

使用Memento模式可以實現應用程序的以下功能。

  • Undo(撤銷)
  • Redo(重做)
  • History(歷史記錄)
  • Snapshot(快照)

Memento模式事先將某個時間點的實例的狀態保存下來,之後在必要時,再將實例恢復至當時的狀態。

示例程序

類的一覽表

名字 說明
Memento 表示Gamer狀態的類
Gamer 表示遊戲主人公的類。它會生成Memento的實例
Main 進行遊戲的類。它會事先保存Memento的實例,之後會根據需要恢復Gamer的狀態。

示例程序類圖

程序功能:

  • 遊戲是自動進行的
  • 遊戲的主人公通過擲骰子來決定下一個狀態
  • 當骰子點數爲1的時候,主人公的金錢會增加
  • 當骰子點數爲2的時候,主人公的金錢會減少
  • 當骰子點數爲6的時候,主人公會得到水果
  • 主人公沒有錢的時候遊戲會結束
    pic1

Memento

import java.util.*;

public class Memento {
    int money;                              // 所持金錢
    ArrayList fruits;                       // 當前獲得的水果
    public int getMoney() {                 // 獲取當前所持金錢(narrow interface)
        return money;
    }
    Memento(int money) {                    // 構造函數(wide interface)
        this.money = money;
        this.fruits = new ArrayList();
    }
    void addFruit(String fruit) {           // 添加水果(wide interface)
        fruits.add(fruit);
    }
    List getFruits() {                      // 獲取當前所持所有水果(wide interface)
         return (List)fruits.clone();
    }
}

Gamer

import java.util.*;

public class Gamer {
    private int money;                          // 所持金錢
    private List fruits = new ArrayList();      // 獲得的水果
    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 {                                    // 骰子結果爲3、4、5則什麼都不會發生
            System.out.println("什麼都沒有發生。");
        }
    }
    public Memento createMemento() {                // 拍攝快照
        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();
    }
    @Override
    public String toString() {                      // 用字符串表示主人公狀態
        return "[money = " + money + ", fruits = " + fruits + "]";
    }
    private String getFruit() {                     // 獲得一個水果
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "好吃的";
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
    }
}

Main

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("");
        }
    }
}

示例程序時序圖

pic2

Memento 模式中登場的角色

1.Originator(生成者)

Originator角色會在保存自己的最新狀態時生成Memento角色。當把以前保存的Memento角色傳遞給Originator角色時,它會將自己恢復至生成該Memento角色時的狀態。在示例程序中,由Gamer類扮演此角色。

2.Memento(紀念品)

Memento角色會將Originator角色的內部信息整合在一起。在Memento角色中雖然保存了Originator角色的信息,但它不會向外公佈這些信息。
Memento 角色有以下兩種接口(API)。

  • wide interface——寬接口(API)
    Memento角色提供的“寬接口(API)”是指所有用於獲取恢復對象狀態信息的方法的集合。由於寬接口(API)會暴露所有Memento角色的內部信息,因此能夠使用寬接口(API)的只有Originator角色。
  • narrow interface——窄接口(API)
    Memento角色爲外部的Caretaker角色提供了“窄接口(API)”。可以通過窄接口(API)獲取的Memento角色的內部信息非常有限,因此可以有效地防止信息泄漏。
    通過對外提供以上兩種接口(API),可以有效地防止對象的封裝性被破壞。
    在示例程序中,由Memento類扮演此角色。
    Originator角色和Memento角色之間有着非常緊密的聯繫。

3.Caretaker(負責人)

當Caretaker角色想要保存當前的Originator角色的狀態時,會通知Originator角色。Originator角色在接收到通知後會生成Memento角色的實例並將其返回給Caretaker角色。由於以後可能會用Memento實例來將Originator恢復至原來的狀態,因此Caretaker角色會一直保存Memento實例。在示例程序中,由Main類扮演此角色。
不過,Caretaker角色只能使用Memento角色兩種接口(API)中的窄接口(API),也就是說它無法訪問Memento角色內部的所有信息。它只是將Originator角色生成的Memento角色當作一個黑盒子保存起來。
雖然Originator角色和Memento角色之間是強關聯關係,但Caretaker角色和Memento角色之間是弱關聯關係。Memento角色對Caretaker角色隱藏了自身的內部信息。

接口(API)寬窄的含義補充

“窄”的意思是指外部可以操作的類的內部的內容很少。“寬”則與之相反。

通用類圖

pic3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章