在使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。

  要想恢复实例,需要一个可以自由访问实例内部结构的权限。但是,如果稍有不注意,又可能会将依赖于实例内部结构的代码分散地编写在程序的各种地方,导致程序变得难以维护。这种情况就叫做“破坏了封装性”。

  通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。这就是Memento模式。

  使用Memento可以实现撤销、重做、历史记录、快照等功能。它事先将某个时间点的实例的状态保存下来,之后在有必要时,再将实例恢复至当时的状态。

  首先看一下示例程序的类图。

  这个示例程序时一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下:

  1.游戏是自动进行。                                

  2.游戏的主人公通过掷骰子来决定下一个状态。

  3.骰子点数为1,金钱增加。

  4.骰子点数为2,金钱减少。                                 

  5.骰子点数为6,得到水果。                                 

  6.主人公没钱的时候游戏结束。

  在程序中,如果金钱增加,为了方便将来恢复状态,会生成Memento实例,将现在的状态保存起来。所保存的数据为当前只有的金钱和水果。如果不断掷骰子导致金钱减少,为了防止金钱变为0而结束游戏,我们会使用Memento实例将游戏恢复至之前的状态。

 package bigjunoba.bjtu.game;

 import java.util.*;

 public class Memento {

     int money;                              // 所持金钱
ArrayList<String> fruits;
// 当前获得的水果
public int getMoney() { // 获取当前所持金钱(narrow interface)
return money;
} Memento(int money) { // 构造函数(wide interface)
this.money = money;
this.fruits = new ArrayList<String>();
} void addFruit(String fruit) { // 添加水果(wide interface)
fruits.add(fruit);
} @SuppressWarnings("unchecked")
List<String> getFruits() { // 获取当前所持所有水果(wide interface)
return (List<String>)fruits.clone();
} }

  Memento类。这里没有将money和fruits的可见性设为private,是因为希望同在game包下的Gamer类可以访问者两个字段。Memento构造函数也不是public,因此并不是任何其他类都可以生成Memento类的实例。只有在同一个包下的类(即Gamer类)才能调用Memento类的构造函数。addFruit方法也不是public,还有getFruits方法也不是public,这个是因为只有同一个包下的其他类才能添加水果,无法从game包外部改变Memento内部的状态。

 package bigjunoba.bjtu.game;
import java.util.*; public class Gamer {
private int money; // 所持金钱
private List<String> fruits = new ArrayList<String>(); // 获得的水果
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<String> 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)];
}
}

  Gamer类是表示游戏主人公的类。进行游戏的主要方法时bet方法。createMemento方法是主要的方法,作用是保存当前的状态(快照)。根据当前时间点所持有的金钱和水果生成一个Memento类的实例,该实例代表了“当前Gamer的状态”,它会被返回给调用者。restoreMemento类作为撤销方法,它会根据接收到的Memento类的实例来讲Gamer恢复为以前的状态。

 package bigjunoba.bjtu.test;

 import bigjunoba.bjtu.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("");
}
}
}

  Main类为测试类,在变量memento中保存了“某个时间点的Gamer的状态”。如果运气很好,金钱增加了,就保存现在的状态,如果运气不好,金钱不足了,就会恢复到memento状态。

==== 0
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
(所持金钱增加了许多,因此保存游戏当前的状态)
==== 1
当前状态:[money = 200, fruits = []]
获得了水果(葡萄)。
所持金钱为200元。 ==== 2
当前状态:[money = 200, fruits = [葡萄]]
获得了水果(好吃的橘子)。
所持金钱为200元。 ==== 3
当前状态:[money = 200, fruits = [葡萄, 好吃的橘子]]
什么都没有发生。
所持金钱为200元。 ==== 4
当前状态:[money = 200, fruits = [葡萄, 好吃的橘子]]
所持金钱减半了。
所持金钱为100元。 ==== 5
当前状态:[money = 100, fruits = [葡萄, 好吃的橘子]]
获得了水果(苹果)。
所持金钱为100元。 ==== 6
当前状态:[money = 100, fruits = [葡萄, 好吃的橘子, 苹果]]
所持金钱减半了。
所持金钱为50元。
(所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 7
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。 ==== 8
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。 ==== 9
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。 ==== 10
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。 ==== 11
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。 ==== 12
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。 ==== 13
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。 ==== 14
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。 ==== 15
当前状态:[money = 100, fruits = []]
所持金钱减半了。
所持金钱为50元。
(所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 16
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。 ==== 17
当前状态:[money = 100, fruits = []]
获得了水果(香蕉)。
所持金钱为100元。 ==== 18
当前状态:[money = 100, fruits = [香蕉]]
所持金钱减半了。
所持金钱为50元。
(所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 19
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。 ==== 20
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

  测试结果。这个测试结果有点非酋,看懂就行了。

  示例程序的时序图。

  Memento模式的类图。

设计模式(十八)Memento模式的更多相关文章

  1. 【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成 ...

  2. 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...

  3. 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)

      设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的 ...

  4. 设计模式 ( 十九 ) 模板方法模式Template method(类行为型)

      设计模式 ( 十九 ) 模板方法模式Template method(类行为型) 1.概述 在面向对象开发过程中,通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行 ...

  5. C++设计模式实现--备忘录(Memento)模式

    一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...

  6. 设计模式 ( 十八 ):State状态模式 -- 行为型

    1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ellse语句来做状态判断来进行不同情况的处理.但是对 ...

  7. C#设计模式之十八状态模式(State Pattern)【行为型】

    一.引言 今天我们开始讲“行为型”设计模式的第六个模式,该模式是[状态模式],英文名称是:State Pattern.无论是现实世界,还是面向对象的OO世界,里面都有一个东西,那就是对象.有对象当然就 ...

  8. java常用设计模式十:模板模式

    一.定义 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 如果上面的话不好理解,请看下面的例子 二.示例 1)定义一个模 ...

  9. 《Head First 设计模式》[01] 策略模式

    <Head First 设计模式>(点击查看详情) 1.写在前面的话 之前在列书单的时候,看网友对于设计模式的推荐里说,设计模式的书类别都大同小异,于是自己就选择了Head First系列 ...

  10. Java 设计模式系列(十八)备忘录模式(Memento)

    Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...

随机推荐

  1. win10 更新之后,软件路径被改为*

    win 10 更新到最新版之后,软件安装盘符被改为* ,导致软件打开失败,截图如下: 1. 首先先下载一个RegistryWorkshop 地址:https://sm.myapp.com/origin ...

  2. 基于python的scrapy环境搭建

    0.1安装python软件 32位机的电脑安装python-3.6.3.exe 64位机的电脑安装python-3.6.3-amd64.exe 0.1.1 python环境搭建 执行安装程序 选择Ad ...

  3. node学习笔记(一)本地文件目录查看器

    Node.js 新闻 nw.js 前端开发桌面应用 内容 node.js实战 照例提供百度云链接,本来以为是实战系列的那本,但不是,不过这本也不错 链接:https://pan.baidu.com/s ...

  4. ArcGISEngine中GP工具奇怪错误问题error(s) have been detected for layer

    运行时环境:使用CADToGeodatabase工具执行DWG文件转gdb过程,多次执行(即执行完一个dwg转gdb,再执行另一个dwg转gdb),执行失败 错误描述:首先执行CADToGeodata ...

  5. RabbitMQ原理介绍

    RabbitMQ历史 RabbitMQ消息系统是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.在同步消息通讯的世界里有很多公开标准(如COBAR的IIO ...

  6. httpSession和Cookie

    1.session在何时被创建一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true) ...

  7. Spring5源码解析3-refresh方法初探

    接上回分析完register(annotatedClasses);后,现在来看一下refresh();方法. // new AnnotationConfigApplicationContext(App ...

  8. [LeetCode] 822. Card Flipping Game

    Description On a table are N cards, with a positive integer printed on the front and back of each ca ...

  9. Linux下mysql相关操作

    Linux下mysql相关操作 1.创建MySQL mysql -u root -p create user 'username'@'%' identified by 'password'; %可以选 ...

  10. javascript随机点名--案例

    主要知识点涉及if选择结构判断语句.数组的定义.定时器.清除定时器.日期对象的使用. 1.HTML结构 <!DOCTYPE html> <html> <head> ...