Memento

概述:

保存对象状态,使用此模式可以实现:Undo(撤销),Redo(重做),History(历史记录),SnapShot(快照)

Memento_sample


说明:

1.本文仅仅是对 图解设计模式 的简单总结(个人笔记).
2.文章中的图片均来自本书,添加水印只是为了防止盗链行为,并无侵权的想法.
综上,若侵权, 请联系删除!
转载请标注出处!


案例:

  • 案例说明:

    搜集水果和获取金钱数的投掷骰子游戏, 游戏规则如下

    • 游戏自动进行
    • 游戏主人公通过掷色子决定下一个状态
    • 当点数为1 时,主人公金钱会增加
    • 当点数为 2时, 金钱减少
    • 当点数为 6时, 主人公会得到水果
    • 主人公没有金钱游戏就会结束
      • Memento_uml_case
  • 主要代码:

  • Main.java

    package Gof.Memento;
    
    import Gof.Memento.game.Gamer;
    import Gof.Memento.game.Memento;
    
    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("");
            }
        }
    }
    
  • Game.java

    package Gof.Memento.game;
    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();
        }
        public String toString() {                      // 用字符串表示主人公状态
            return "[money = " + money + ", fruits = " + fruits + "]";
        }
        private String getFruit() {                     // 获得一个水果
            String prefix = "";
            if (random.nextBoolean()) {
                prefix = "好吃的";
            }
            return prefix + fruitsname[random.nextInt(fruitsname.length)];
        }
    }
    
  • 时序图:

  • uml_sqence

  • 代码链接:传送门

    uml综述Memento_uml

  • Originator(生成者): originator会保存自己最新状态时生成的Memento角色,当把以前保存的Memento橘色传递给originator角色事,他自己恢复至生成该Memento角色时的状态

  • Memento:Memento橘色回见originator橘色的内部信息整合在一起(不会向外传递这些消息)

    • wide interface:指所有用于获取恢复对象状态信息的方法的集合(会暴露内部信息,所以只有originator角色使用此接口)
    • narrowinterface: 可以获取originator部分信息,可以防止信息泄露
  • Caretaker(负责人): 只使用narrowinterface,只是将originator角色生成的Memento角色当做一个黑盒子保存起来

  • 补充: originator 与 Memento角色是强关联,Memento与Caretaker是弱关联, Memento角色对Caretaker角色隐藏了自身的内部信息

收获:

  • Memento有效期: 在内存中,不必考虑有效期,在文件中,需要考虑,若我们在某个时间点将数据保存在文件中,之后又升级了应用程序,那么可能会出现与原来保存的Memento与当前的应用程序不匹配情况

  • 这个设计模式很好的体现了public,private,protected关键字的魅力,用好关键字,会对我们的程序有着意想不到的好处

  • Caretaker与originator 的职责分工:

    • Caretaker角色决定何时拍摄快照,何时撤销以及保存Memento角色
    • originator角色的职责是生成Memento角色,和使用M接受到的Memento角色来恢复自己的状态
  • Caretaker与originator的职责分工可实现:

    • 变更为可以多次撤销
    • 变更为不仅可以撤销,还可以将现在的状态保存在文件中

相关设计模式:

  • Command:使用Memento实现撤销功能
  • Protype:生成完全一模一样的实例,内容完全相同,Memento保存角色当前的状态,保存的信息仅是恢复时所需的那部分信息
  • State: 用类表示状态,
  • Memento : 用实例表示状态

注意:

此部分内容属于对GOF Design Pattern知识的初步认知阶段,参考书籍是结城浩的《图解设计模式》,简单易懂,十分推荐!
以上内容,作者一字一句码出来的,纯属不易,欢迎大家转载,转载是还请您表明出处。另外如果我有侵权行为,请在下方留言,确认后我会及时撤销相应内容,谢谢大家!

PS:欢迎大家来到我的小站,鸣谢!