设计模式(十八)-备忘录模式(Memento Pattern)——保存对象状态

备忘录模式(Memento Pattern)可以引入表示实例状态的角色,可以在保存和恢复实例时有效的防止对象的封装性遭到破坏。

使用备忘录模式可以实现应用程序的一下功能:

1、Undo(撤销)

2、Redo(重做)

3、History(历史记录)

4、Snapshot(快照)

备忘录模式就是一个事先将某个时间点的实例的状态保存下来,之后在必要的时,再将实例恢复至当时的状态。

Memento.java

package com.test.dp.Memento.Sample.game;

import java.util.*;

//表示Gamer状态的类
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.java

package com.test.dp.Memento.Sample.game;

import java.util.*;

//表示游戏主人公的类。它会生成Memento的实例
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();
    }
    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.java

package com.test.dp.Memento.Sample;

import com.test.dp.Memento.Sample.game.Memento;
import com.test.dp.Memento.Sample.game.Gamer;

//进行游戏的类。它会事先保存Memento的实例,之后回根据需要恢复Gamer的状态
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("");
        }
    }
}
执行结果:

==== 0
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
    (所持金钱增加了许多,因此保存游戏当前的状态)

==== 1
当前状态:[money = 200, fruits = []]
获得了水果(葡萄)。
所持金钱为200元。

==== 2
当前状态:[money = 200, fruits = [葡萄]]
什么都没有发生。
所持金钱为200元。

==== 3
当前状态:[money = 200, fruits = [葡萄]]
所持金钱增加了。
所持金钱为300元。
    (所持金钱增加了许多,因此保存游戏当前的状态)

==== 4
当前状态:[money = 300, fruits = [葡萄]]
所持金钱减半了。
所持金钱为150元。

==== 5
当前状态:[money = 150, fruits = [葡萄]]
获得了水果(好吃的橘子)。
所持金钱为150元。

(中间省略)

==== 21
当前状态:[money = 400, fruits = [香蕉]]
什么都没有发生。
所持金钱为400元。

==== 22
当前状态:[money = 400, fruits = [香蕉]]
获得了水果(葡萄)。
所持金钱为400元。

==== 23
当前状态:[money = 400, fruits = [香蕉, 葡萄]]
获得了水果(好吃的葡萄)。
所持金钱为400元。

(以下省略)

总结:

应用实例:1、后悔药。2、打游戏时的存档。3、Windows 里的 ctri + z。4、IE 中的后退。4、数据库的事务管理。

优点:1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景:1、需要保存/恢复数据的相关状态场景。2、提供一个可回滚的操作。

注意事项:1、为了符合迪米特原则,还要增加一个管理备忘录的类。2、为了节约内存,可使用原型模式+备忘录模式。


你可能感兴趣的:(设计模式)