情景引入
我们在使用文本编辑器编写文件时,如果不小心删除了某句话,可以通过撤销(undo)功能将
文件恢复至之前的状态。有些文本编辑器甚至支持多次撤销,能够恢复至很久之前的版本。
使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销
时,还需要根据所保存的信息将实例恢复至原来的状态。
要想恢复实例,需要-个可以自由访问实例内部结构的权限。但是,如果稍不注意,又可能会
将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护。这种情况
就叫作“破坏了封装性”。
通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。
这就是我们在本章中要学习的Memento模式。
使用Memento模式可以实现应用程序的以下功能。
- Undo(撤销)
- Redo(重做)
- History(历史记录)
- Snapshot (快照)
Memento有“纪念品”“遗物”“备忘录”的意思。
当大家从抽屉中拿出让人怀念的照片时,肯定会感慨万千,感觉仿佛又回到了照片中的那个时
候。Memento模式就是一个这样的设计模式, 它事先将某个时间点的实例的状态保存下来,之后在
有必要时,再将实例恢复至当时的状态。
示例程序
功能描述
这是一个收集水果和获取金钱数的掷骰子游戏,游戏规则很简单,具体如下。
- 游戏是自动进行的
- 游戏的主人公通过掷骰子来决定下一个状态
- 当骰子点数为1的时候,主人公的金钱会增加
- 当骰子点数为2的时候,主人公的金钱会减少
- 当骰子点数为6的时候,主人公会得到水果
- 主人公没有钱时游戏就会结束
在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在
的状态保存起来。所保存的数据为当前持有的金钱和水果。如果不断掷出了会导致金钱减少的点
数,为了防止金钱变为0而结束游戏,我们会使用Memento的实例将游戏恢复至之前的状态。
(memento 应用于存档/快照)
类的一览表
包 |
名字 |
说明 |
game |
Memento |
表示 Gamer的状态的类 |
game |
Gamer |
表示游戏主人公的类。它会生成 Memento的实例 |
无 |
Main |
进行游戏的类。它会事先保存 Memento的,之后会根据需要恢复 Gamer的状态 |
UML

主要代码
Memento 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Memento { int money; ArrayList fruits; public int getMoney(){ return money; }
public Memento(int money) { this.money = money; this.fruits = new ArrayList(); } void addFruit(String fruit){ fruits.add(fruit); }
List getFruits() { return (List)fruits.clone(); } }
|
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 59 60 61 62 63
| public class Gamer { private int money; private List fruits = new ArrayList(); private Random random = new Random(); private static String[] FRUIT_NAMES = { "苹果", "葡萄", "香蕉", "橘子" };
public Gamer(int money) { this.money = money; }
public int getMoney() { return money; }
public void bet() { int dice = random.nextInt(6) + 1; if (1 == dice) { money += 100; System.out.println("所持金钱增加了。"); } else if (2 == dice) { money /= 2; System.out.println("所持金钱减少了。"); } else if (6 == dice) { String f = getFruit(); System.out.println("获得了水果(" + f + ")."); }else { 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.getMoney(); this.fruits = memento.getFruits(); }
@Override public String toString() { return "Gamer{" + "money=" + money + ", fruits=" + fruits + '}'; }
private String getFruit() { String prefix = ""; if (random.nextBoolean()) { prefix = "好吃的"; } return prefix + FRUIT_NAMES[random.nextInt(FRUIT_NAMES.length)]; } }
|
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
| public class Main { 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){ System.out.println("");
} } } }
|