0%

设计模式之Memento模式

备忘录模式用于保存和会复发对象的状态.比如在玩游戏的时候有一个保存当前闯关的状态的功能,会对当前用户所处的状态进行保存,当用户闯关失败或者需要从快照的地方开始的时候,就能读取当时保存的状态完整的恢复到当前的环境,这一点和VMware上面的快照功能很类似.

介绍

示例程序

  1. Memento.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.edu.tju.GOF.Memento.game;

    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();
    }
    }
  2. Gamer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package com.edu.tju.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)];
    }
    }
  3. Main
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.edu.tju.GOF.Memento;

    import com.edu.tju.GOF.Memento.game.*;

    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("");
    }
    }
    }
    游戏的功能是根据循环次数随机生成1~6六个数,如果数字是1,则金钱加一百,如果是2,则金钱减半,如果是6,则随机生成水果,水果分为好吃和不好吃的,在保存的时候只保存好吃的水果,恢复的时候就只有好吃的水果了.当金钱少于当前备忘录中的金钱的一半时的时候就要恢复到备忘录的状态,当金钱大于备忘录状态的时候就要备份当前的状态,备份的时候只备份好吃的水果以及当前金额.

角色

  • Originator(生成者,被保存的对象)

  • Memento(纪念品,存档)
    Memento角色会将Originator角色的内部信息整合在一起,在Memento角色中虽然保存了Originator角色的信息,但它不会向外部公开这些信息.
    Memento 角色有一下两种接口(API)

    • 宽接口
      Memento角色提供的宽接口时指所有用于获取恢复对象状态信息的方法的集合.由于宽接口会暴露所有Memento角色的内部信息,因此能够使用宽接口的只有Originator角色

    • 窄接口
      为外部的Caretaker角色提供了窄接口.可以通过窄接口获取Memento角色的内部信息非常有限,因此可以有效地方志信息泄露.

      通过对外提供两种接口,可以有效地防止对象的封装性被破坏.

  • Caretaker(责任人,调用者)

与其他模式的关系

  1. 原型模式也能保存一个对象在某一个时刻的状态,但是原型模式保存的是当前对象的所有状态信息,恢复的时候会生成与保存的对象完全相同的另外一个实例;而备忘录模式保存的是我们关心的在恢复时需要的对象的部分状态信息,相当于快照。

备忘录模式

Thank you for your support