设计模式(十八)Memento模式
在使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。
要想恢复实例,需要一个可以自由访问实例内部结构的权限。但是,如果稍有不注意,又可能会将依赖于实例内部结构的代码分散地编写在程序的各种地方,导致程序变得难以维护。这种情况就叫做“破坏了封装性”。
通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。这就是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模式的更多相关文章
- 【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成 ...
- 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...
- 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)
设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的 ...
- 设计模式 ( 十九 ) 模板方法模式Template method(类行为型)
设计模式 ( 十九 ) 模板方法模式Template method(类行为型) 1.概述 在面向对象开发过程中,通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行 ...
- C++设计模式实现--备忘录(Memento)模式
一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...
- 设计模式 ( 十八 ):State状态模式 -- 行为型
1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ellse语句来做状态判断来进行不同情况的处理.但是对 ...
- C#设计模式之十八状态模式(State Pattern)【行为型】
一.引言 今天我们开始讲“行为型”设计模式的第六个模式,该模式是[状态模式],英文名称是:State Pattern.无论是现实世界,还是面向对象的OO世界,里面都有一个东西,那就是对象.有对象当然就 ...
- java常用设计模式十:模板模式
一.定义 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 如果上面的话不好理解,请看下面的例子 二.示例 1)定义一个模 ...
- 《Head First 设计模式》[01] 策略模式
<Head First 设计模式>(点击查看详情) 1.写在前面的话 之前在列书单的时候,看网友对于设计模式的推荐里说,设计模式的书类别都大同小异,于是自己就选择了Head First系列 ...
- Java 设计模式系列(十八)备忘录模式(Memento)
Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...
随机推荐
- 第六届蓝桥杯java b组第二题
立方变自身 观察下面的现象,某个数字的立方,按位累加仍然等于自身. 1^3 = 1 8^3 = 512 5+1+2=8 17^3 = 4913 4+9+1+3=17 … 请你计算包括1,8,17在内, ...
- impala对元数据的界面更新操作
执行 impala-shell 即能进入界面操作sql.如果在hive更新了数据之后,而在impala中却无法看到更新后的数据的话,意味着impala里元数据信息还没有刷新,此时在impala操作界面 ...
- redis-分片(转)
转:https://www.cnblogs.com/houziwty/p/5167075.html 分片(partitioning)就是将你的数据拆分到多个 Redis 实例的过程,这样每个实例将只包 ...
- codeforce-1201-C题解
题目:给你一个包含n个整数的数组A(n为奇数),对A做k次以下操作: 对数组排序使数组以非递减顺序排列. 选取数组的中位数,然后加一 最终使得数组的中位数最大. 输入:第一行输入两个数字 n 和 k ...
- springboot 集成druid
1.druid简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.JBo ...
- 夯实Java基础系列14:深入理解Java枚举类
目录 初探枚举类 枚举类-语法 枚举类的具体使用 使用枚举类的注意事项 枚举类的实现原理 枚举类实战 实战一无参 实战二有一参 实战三有两参 枚举类总结 枚举 API 总结 参考文章 微信公众号 Ja ...
- Python PDB调试处理
pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点.单步调试.进入函数调试.查看当前代码.查看栈片段.动态改变变量的值等.pdb 提供了 ...
- vs2019下载和更新速度非常慢的解决方案
纵观全网,就两种解决方案 1.取消网络适配器里面的IPV6的给勾选[我试过,无效,但是有些网友说是有效果,也不知道是咋回事] 2.修改host.[本人亲测有效果,速度从5kb到5M左右,但是无法跑满宽 ...
- 刷新:重新发现.NET与未来
微软在比尔·盖茨手中创立并崛起, 成为PC互联网时代的霸主,很多70,80后都有MVP Ediwang的体验: 当时的微软对我来说就是神的存在.因为我认识电脑到使用电脑的一切几乎都离不开这家伟大的公司 ...
- MySQL 和 Navicat Premium 下载及安装全过程
前言: 我对 “MySQL社区版” 的理解是:它只是一个后台服务,它的管理需要用到其他的数据库管理软件,这里我用的是 Navicat Premium,这个软件可以同时为多个数据库提供管理,比如MySQ ...