1. 程式人生 > >Memento模式(備忘錄設計模式)

Memento模式(備忘錄設計模式)

Memento模式?

使用面向物件程式設計的方式實現撤銷功能時,需要事先儲存例項的相關狀態資訊。然後,在撤銷時,還需要根據所儲存的資訊將例項恢復至原來的狀態。這個時候你需要使用Memento設計模式。(以及例項實現對狀態的儲存)

  • 關鍵字: 1.·Undo(撤銷) 2.·Redo(重做) 3.·History(歷史記錄) 4。·Snapshot(快照)
  • 破壞封裝性: 將依賴於例項內部結構的程式碼分散地編寫在程式中的各個地方,導致程式變得難以維護。

  • 寬窄介面
  1. wide interface——寬介面(APl)Memento角色提供的“寬介面(API)”是指所有用於獲取恢復物件狀態資訊的方法的集合。由於寬介面(API)會暴露所有Memento角色的內部資訊,因此能夠使用寬介面(API)的只有Originator角色。
  2. narrowinterface——窄介面(API)Memento角色為外部的Caretaker角色提供了“窄介面(API)”。可以通過窄介面(API)獲取的Memento角色的內部資訊非常有限,因此可以有效地防止資訊洩露。 通過對外提供以上兩種介面(API),可以有效地防止物件的封裝性被破壞
  • 相關設計模式 1.Command模式(第22章)在使用Command模式處理命令時,可以使用Memento模式實現撤銷功能。 2.Protype模式(第6章)在Memento模式中,為了能夠實現快照和撤銷功能,儲存了物件當前的狀態。儲存的資訊只是在恢復狀態時所需要的那部分資訊。 而在Protype模式中,會生成一個與當前例項完全相同的另外一個例項。這兩個例項的內容完全一樣。
  1. State模式(第19章)在Memento模式中,是用“例項”表示狀態。而在State模式中,則是用“類”表示狀態。

理清職責

  • 實現功能
  1. ·遊戲是自動進行的
  2. ·遊戲的主人公通過擲骰子來決定下一個狀態
  3. ·當骰子點數為1的時候,主人公的金錢會增加·當骰子點數為2的時候,主人公的金錢會減少
  4. ·當骰子點數為6的時候,主人公會得到水果
  5. ·主人公沒有錢時遊戲就會結束

包====>>>名字=====>>>說明 game |Memento|表示Gamer狀態的類 game |Gamer表示遊戲主人公的類。它會生成Memento的例項進行遊戲的類。它會事先儲存Memento的例項,之後會根據需要恢復Gamer的狀態 null | MainT 這裡為了方便起見使用MainT作為責任人儲存使用者狀態

UML

時序圖:

Code

  • Gamer

public class Gamer {

    /**
     * 下面的money 與 fruits 就是按照一般的定義方式去定義
     * 但是我們提取Memento的時候需要注意這個的獲取規則
     */
    // 獲得金錢
    private int money;

    // 獲得的水果
    private List<String> fruits=new ArrayList<>();

    private Random random=new Random();

    private final static String[] fruitname=new String[]{
            "蘋果","葡萄","香蕉","橘子"
    };

    public Gamer(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    /**
     * 開始遊戲
     * 骰子結果1,2 ,6進行不同的操作
     */
    public void bet(){
        int dice=random.nextInt(6)+1;
        if(dice==1){
            this.money+=100;
            System.out.println("金錢增加了!");
        }else if(dice==2){
            this.money/=2;
            System.out.println("金錢減半了!");
        }else if(dice==6){
            String f=getFruit();
            System.out.println("獲得了水果["+f+"]!");
            this.fruits.add(f);
        }else{
            System.out.println("什麼也不發生");
        }
    }

    /**
     * 快照方法
     */
    public Memento createMemento(){
        Memento memento = new Memento(this.money);
        Iterator<String> iterator = fruits.iterator();

        while (iterator.hasNext()){
            String s = iterator.next();
            if(s.startsWith("好吃的")){
                memento.addFruit(s);
            }
        }
        return memento;
    }

    /**
     * 撤銷方法
     */
    public void restoreMemento(Memento memento){
        this.money=memento.money;
        this.fruits=memento.fruits;
    }



    private String getFruit() {
        String prefix="";
        if(random.nextBoolean()){
            prefix="好吃的";
        }
        return prefix+fruitname[random.nextInt(fruitname.length)];
    }

    @Override
    public String toString() {
        return "Gamer{" +
                "money=" + money +
                ", fruits=" + fruits +
                '}';
    }
}

  • Memento

public class Memento {

    /**
     * 使用過程中因為Memento與Gamer是強關聯關係,但是又因為是在同一個game包下,
     * 使用可見性修飾符顯得比較重要:
     * 這裡的兩個欄位在同一個包下都是可以訪問
     */
    int money;
    ArrayList<String> fruits;

    /**
     * 窄介面
     */
    public int getMoney(){
        return money;
    }

    /**
     * 這裡是寬介面
     * @param money
     */
     Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList<>();
    }

    /**
     * 這裡是寬介面
     */
    void addFruit(String fruit){
         fruits.add(fruit);
    }

    /**
     * 這裡是寬介面
     */
    ArrayList<String> getFruits(){
         return (ArrayList<String>) fruits.clone();
    }
}






  • MainT
public class MainT {

    /**
     * 這裡的狀態只是單個快照點,當你需要多個快照點的時候,
     * 單獨建立一個snapshot類來管理,可以使用集合等,
     * 這裡寫個例子
     */
    public static void main(String[] args) {

        Gamer gamer = new Gamer(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()+"元");

            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){
                e.printStackTrace();
            }
        }

    }
}


public class SnapShotManger implements Serializable {

    private List<Memento> mementos=new ArrayList<>();

    /**
     * 實現java.io.Serializable介面
     * 用objectoutputstream的writeobject方法
     * 用objectInputStream的 readobject方法
     */

    /**
     * 儲存
     */

    /**
     * 恢復
     */


}