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

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

  通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。这就是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. WebApi简介

    简单创建.NET Core WebApi:https://www.cnblogs.com/yanbigfeg/p/9197375.html 登陆验证四种方式:https://www.cnblogs.c ...

  2. 要不要学习Git(分布式版本控制系统)

    做技术的人,要不要学一学Git呢? 提出这个问题,是因为很多小伙伴还不会使用Git. 对于任何新一代的技术工具,它在业界普及都有一个过程,Git的阻碍是:学习成本.工具迭代的成本. SVN诞生于200 ...

  3. phpStudy后门漏洞利用复现

    phpStudy后门漏洞利用复现 一.漏洞描述 Phpstudy软件是国内的一款免费的PHP调试环境的程序集成包,通过集成Apache.PHP.MySQL.phpMyAdmin.ZendOptimiz ...

  4. 阿里云服务器ecs配置之安装tomcat

    1.下载链接:https://tomcat.apache.org/download-70.cgi,选择需要的版本下载(.tar.gz文件后缀) 2.通过Xshell.Xftp上传至CentosX的 某 ...

  5. 向net core 3.0进击——Swagger的改变

    目录 前言 引入 测试 小结 前言 十一小长假在不知不觉间可都没了,在这个小尾巴的空隙,把这两天鼓捣的net core 3.0升级过程记录一下,首先还是根据之前的顺序一个个补充进来,先从Swagger ...

  6. python selenium模拟登陆qq空间

    不多说.直接上代码 from selenium import webdriver driver = webdriver.Chrome() driver.get('http://qzone.qq.com ...

  7. 2019 中国.NET 开发者峰会正式启动

    2014年微软组织并成立.NET基金会,微软在成为主要的开源参与者的道路上又前进了一步.2014年以来已经有众多知名公司加入.NET基金会,Google,微软,AWS三大云厂商已经齐聚.NET基金会, ...

  8. ES(Elastic Search)update操作设置无 docment时进行insert

    最近使用一套数据加工中间工具,查看es操作中的update操作.其中方法命名为updateOrInsert.但是没发现代码中有ES的insert方法调用.于是仔细分析了代码逻辑. 经过一路追溯,直至E ...

  9. LeetCode_225-Implement Stack using Queues

    使用队列实现栈的操作 class MyStack { public: /** Initialize your data structure here. */ MyStack() { } /** Pus ...

  10. C# 获取顶级(一级)域名方法

    /// <summary> /// 获取域名的顶级域名 /// </summary> /// <param name="domain">< ...