設計模式學習筆記——備忘錄(Memento)模式

設計模式學習筆記——備忘錄(Memento)模式

@(設計模式)[設計模式, 備忘錄模式, memento]

基本介紹

備忘錄模式提供的基本功能是:保存對象狀態信息(快照)、撤銷、重做和歷史記錄。
備忘錄模式一般會提供兩種接口:寬接口和窄接口。通過寬接口可以獲取整個對象狀態,會暴露備忘錄對象的內部信息。通過窄接口,只能訪問有限的,開發者限制了的信息,可以有效的防止信息泄露。

備忘錄案例

類圖

備忘錄案例類圖

實現代碼

Memento類

package com.pc.memento.example;

import java.util.ArrayList;
import java.util.List;

/**
 * 備忘錄類
 * Created by Switch on 2017/3/31.
 */
public class Memento {
    /**
     * 所持金錢
     */
    private int money;
    /**
     * 獲得的水果
     */
    private List<String> fruits;

    /**
     * 獲取當前所持金錢
     *
     * @return 所持金錢
     */
    public int getMoney() {
        return money;
    }

    /**
     * 構造方法,初始化所持錢數
     *
     * @param money 初始化錢數
     */
    Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList<>();
    }

    /**
     * 添加水果
     *
     * @param fruit 水果
     */
    void addFruit(String fruit) {
        fruits.add(fruit);
    }

    /**
     * 添加當前所持的所有水果
     *
     * @return 水果列表
     */
    List<String> getFruits() {
        return fruits;
    }
}

Gamer類

package com.pc.memento.example;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * 遊戲者類
 * Created by Switch on 2017/3/31.
 */
public class Gamer {
    /**
     * 所持金錢
     */
    private int money;
    /**
     * 獲得的水果
     */
    private List<String> fruits = new ArrayList<>();
    /**
     * 隨機數生成器
     */
    private Random random = new Random();
    /**
     * 表示水果種類的數組
     */
    private static String[] fruitsname = {"蘋果", "葡萄", "香蕉", "橘子"};

    /**
     * 構造方法,初始化金錢數
     *
     * @param money 金錢數
     */
    public Gamer(int money) {
        this.money = money;
    }

    /**
     * 獲取當前所持金錢數
     *
     * @return 金錢數
     */
    public int getMoney() {
        return money;
    }

    /**
     * 投擲骰子進行遊戲
     */
    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 = this.getFruit();
            System.out.println("獲得了水果(" + f + ")。");
            this.fruits.add(f);
        } else {
            System.out.println("什麼都沒有發生。");
        }
    }

    /**
     * 創建備忘錄
     *
     * @return 備忘錄對象
     */
    public Memento createMemento() {
        Memento m = new Memento(money);
        Iterator<String> it = fruits.iterator();
        while (it.hasNext()) {
            String fruit = it.next();
            if (fruit.startsWith("好喫的")) {
                m.addFruit(fruit);
            }
        }
        return m;
    }

    /**
     * 撤銷到指定備忘
     *
     * @param memento 備忘錄對象
     */
    public void restoreMemento(Memento memento) {
        this.money = memento.getMoney();
        this.fruits = memento.getFruits();
    }

    /**
     * 獲得一個水果
     *
     * @return 水果
     */
    private String getFruit() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "好喫的";
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
    }

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

測試類

package com.pc.memento.example.test;

import com.pc.memento.example.Gamer;
import com.pc.memento.example.Memento;
import org.junit.Test;

/**
 * Memento Tester.
 *
 * @author Switch
 * @version 1.0
 */
public class MementoTest {
    /**
     * 測試備忘錄模式
     */
    @Test
    public void testMemento() {
        Gamer gamer = new Gamer(100);
        Memento memento = gamer.createMemento();
        for (int i = 0; i < 20; 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(100);
            } catch (InterruptedException e) {
            }
            System.out.println("");
        }
    }
}

運行結果

==== 0
當前狀態:Gamer{ money=100, fruits=[]}
什麼都沒有發生。
所持金錢爲100元。

==== 1
當前狀態:Gamer{ money=100, fruits=[]}
所持金錢減半了。
所持金錢爲50元。

==== 2
當前狀態:Gamer{ money=50, fruits=[]}
所持金錢減半了。
所持金錢爲25元。
    (所持金錢減少了許多,因此將遊戲恢復至以前的狀態)

==== 3
當前狀態:Gamer{ money=100, fruits=[]}
獲得了水果(好喫的葡萄)。
所持金錢爲100元。

==== 4
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄]}
所持金錢增加了。
所持金錢爲200元。
    (所持金錢增加了許多,因此保存遊戲當前的狀態)

==== 5
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄]}
所持金錢減半了。
所持金錢爲100元。

==== 6
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄]}
獲得了水果(葡萄)。
所持金錢爲100元。

==== 7
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲200元。

==== 8
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
所持金錢減半了。
所持金錢爲100元。

==== 9
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲200元。

==== 10
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
什麼都沒有發生。
所持金錢爲200元。

==== 11
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲300元。
    (所持金錢增加了許多,因此保存遊戲當前的狀態)

==== 12
當前狀態:Gamer{ money=300, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲400元。
    (所持金錢增加了許多,因此保存遊戲當前的狀態)

==== 13
當前狀態:Gamer{ money=400, fruits=[好喫的葡萄, 葡萄]}
所持金錢減半了。
所持金錢爲200元。

==== 14
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
獲得了水果(葡萄)。
所持金錢爲200元。

==== 15
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄]}
獲得了水果(好喫的橘子)。
所持金錢爲200元。

==== 16
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
什麼都沒有發生。
所持金錢爲200元。

==== 17
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
所持金錢增加了。
所持金錢爲300元。

==== 18
當前狀態:Gamer{ money=300, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
所持金錢減半了。
所持金錢爲150元。
    (所持金錢減少了許多,因此將遊戲恢復至以前的狀態)

==== 19
當前狀態:Gamer{ money=400, fruits=[好喫的葡萄]}
什麼都沒有發生。
所持金錢爲400元。

備忘錄模式中的角色

Originator(生成者)

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

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角色之間有着非常緊密的聯繫。

Caretaker(負責人)

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

類圖

備忘錄模式類圖

GitHub:DesignPatternStudy

——————參考《圖解設計模式》

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