游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,可以从备忘录对象恢复到大战前的状态
针对每一种角色,设计一个类来存储该角色的状态
【分析】
Originator(生成者)
:Originator角色会在保存自己的最新状态时生成Memento角色。当把以前保存的Memento角色传递给Originator角色时,它会将自已恢复至生成该Memento角色时的状态Memento(备忘录)
:Memento角色会将Originator 角色的内部信息整合在一起。在 Memento 角色中虽然保存了Originator 角色的信息,但它不会向外部公开这些信息(通过对方法的权限字符进行设置)Caretaker(负责人)
:当Caretaker角色
想要保存当前的Originator角色
的状态时,会通知Originator角色
。Originator角色
在接收到通知后会生成Memento角色
的实例并将其返回给Caretaker角色
。由于以后可能会用Memento实例
来将Originator
恢复至原来的状态,因此Caretaker角色
会一直保存 Memento实例
在示例程序中。Caretaker角色
只能使用Memento角色
的两种接口(API)中的窄接口,也就是说它无法访问Memento角色
内部的所有信息(比如案例三中Caretaker角色
只能获取Memento角色
的金钱信息,无法进行创建Memento实例
等操作)。它只是将Originator角色
生成的Memento角色
当作一个黑盒子保存起来。虽然Originator角色
和Memento角色
之间是强关联关系,但Caretaker角色
和Memento角色
之间是弱关联关系。Memento角色
对Caretaker角色
隐藏了自身的内部信息【划分Caretaker角色
和Originator角色
的意义】
Caretaker角色的职责是决定何时拍摄快照,何时撤销以及保存Memento角色。另一方面,Originator 角色的职责则是生成Memento角色和使用接收到的Memento角色来恢复自己的状态。有了这样的职责分担,当我们需要应对以下的需求变更时,就不用修改Originator角色
【Caretaker角色只能通过窄接口(API)来操作 Memento角色,如果Caretaker角色可以随意地操作Memento角色,会发生什么问题呢】
Originator
:需要被保存状态的对象Memento
:备忘录对象,负责记录Originator对象的状态Caretaker
:守护者对象,负责保存多个备忘录对象,一般使用集合进行管理,提高管理效率【Originator】
package com.test.memento.theory;
public class Originator {
/**
* 角色的状态信息
*/
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 编写一个方法,可以保存一个状态对象 Memento
* 因此编写一个方法,返回 Memento
*
* @return
*/
public Memento saveStateMemento() {
return new Memento(state);
}
/**
* 通过备忘录对象获取原有状态信息,恢复状态
*
* @param memento
*/
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
【Memento】
package com.test.memento.theory;
public class Memento {
/**
* 用来保存状态信息
*/
private String state;
/**
* 构造器
*
* @param state
*/
public Memento(String state) {
super();
this.state = state;
}
/**
* 获取保存的状态信息
* @return
*/
public String getState() {
return state;
}
}
【Caretaker】
package com.test.memento.theory;
import java.util.ArrayList;
import java.util.List;
/**
* 统一管理备忘录对象
*/
public class Caretaker {
/**
* 在 List 集合中会有很多的备忘录对象
* 如果想要保存多个Originator的多个状态,可以使用HashMap>
*/
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento memento) {
mementoList.add(memento);
}
/**
* 获取 Originator 的 第 index 个 备忘录对象(即所保存的状态)
*
* @param index
* @return
*/
public Memento get(int index) {
return mementoList.get(index);
}
}
【主类】
package com.test.memento.theory;
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
// 备忘录对象管理器
Caretaker caretaker = new Caretaker();
originator.setState(" 状态#1 攻击力 100 ");
//保存了当前的状态
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#2 攻击力 80 ");
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#3 攻击力 50 ");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是 =" + originator.getState());
//希望得到状态 1, 将 originator 恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1 , 当前的状态是 =" + originator.getState());
}
}
【运行】
当前的状态是 = 状态#3 攻击力 50
恢复到状态1 , 当前的状态是 = 状态#1 攻击力 100
Process finished with exit code 0
1234
【Originator:GameRole】
package com.test.memento.game;
public class GameRole {
private int vit;
private int def;
/**
* 创建Memento,即根据当前的状态得到Memento
*
* @return
*/
public Memento createMemento() {
return new Memento(vit, def);
}
/**
* 从备忘录对象,恢复GameRole的状态
*
* @param memento
*/
public void recoverGameRoleFromMemento(Memento memento) {
this.vit = memento.getVit();
this.def = memento.getDef();
}
/**
* 显示当前游戏角色的状态
*/
public void display() {
System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
【Memento】
package com.test.memento.game;
public class Memento {
/**
* 攻击力
*/
private int vit;
/**
* 防御力
*/
private int def;
public Memento(int vit, int def) {
this.vit = vit;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
【Caretaker】
package com.test.memento.game;
/**
* 守护者对象, 保存游戏角色的状态
*/
public class Caretaker {
/**
* 因为大战Boss之前只有一个状态,所以这里保存一次状态,不需要使用集合
*/
private Memento memento;
//对GameRole保存多次状态
//private ArrayList mementos;
//对多个GameRole保存多个状态
//private HashMap> rolesMementos;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
【主类】
package com.test.memento.game;
public class Client {
public static void main(String[] args) {
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.setVit(100);
gameRole.setDef(100);
System.out.println("和boss大战前的状态");
gameRole.display();
//把当前状态保存caretaker
Caretaker caretaker = new Caretaker();
caretaker.setMemento(gameRole.createMemento());
System.out.println("和boss大战~~~");
gameRole.setDef(30);
gameRole.setVit(30);
gameRole.display();
System.out.println("大战后,使用备忘录对象恢复到站前");
gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
System.out.println("恢复后的状态");
gameRole.display();
}
}
【运行】
和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到站前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100
Process finished with exit code 0
【Originator:Gamer】
package com.test.memento.Sample.game;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Gamer {
/**
* 所持金钱
*/
private int money;
/**
* 获得的水果
*/
private List fruits = new ArrayList();
/**
* 随机数生成器
*/
private Random random = new Random();
/**
* 表示水果种类的数组
*/
private static String[] fruitsName = {
"苹果", "葡萄", "香蕉", "橘子",
};
/**
* 构造函数
*
* @param money
*/
public Gamer(int money) {
this.money = money;
}
/**
* 获取当前所持金钱
*
* @return
*/
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("什么都没有发生。");
}
}
/**
* 拍摄快照
*
* @return
*/
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;
}
/**
* 撤销到指定状态
*
* @param memento
*/
public void restoreMemento(Memento memento) {
this.money = memento.money;
this.fruits = memento.getFruits();
}
/**
* 用字符串输出主人公的状态
*
* @return
*/
public String toString() {
return "[money = " + money + ", fruits = " + fruits + "]";
}
/**
* 随机获取一个水果
*
* @return
*/
private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsName[random.nextInt(fruitsName.length)];
}
}
【Memento】
package com.test.memento.Sample.game;
import java.util.ArrayList;
import java.util.List;
public class Memento {
/**
* 游戏主人公所持金钱
*/
int money;
/**
* 游戏主人公当前获得的水果
*/
ArrayList fruits;
/**
* 获取当前所持金钱(narrow interface)
* narrow interface:Memento角色为外部的 Caretaker 角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露
* @return
*/
public int getMoney() {
return money;
}
/**
* 构造函数(wide interface),只有相同包的Gamer类才使用该构造方法,因为方法修饰类型是default
*
* @param money
*/
Memento(int money) {
this.money = money;
this.fruits = new ArrayList();
}
/**
* 添加水果(wide interface)
*
* @param fruit
*/
void addFruit(String fruit) {
fruits.add(fruit);
}
/**
* 获取当前所持所有水果(wide interface)
* wide interface:“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator 角色
* @return
*/
List getFruits() {
return (List) fruits.clone();
}
}
【Caretaker】
package com.test.memento.Sample;
import com.test.memento.Sample.game.Gamer;
import com.test.memento.Sample.game.Memento;
public class Main {
public static void main(String[] args) {
// 最初的所持金钱数为100
Gamer gamer = new Gamer(100);
// 保存最初的状态
Memento memento = gamer.createMemento();
for (int i = 0; i < 10; 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("");
}
}
}
【运行】
==== 0
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。
==== 1
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
(所持金钱增加了许多,因此保存游戏当前的状态)
==== 2
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 3
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 4
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 5
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 6
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 7
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。
==== 8
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。
==== 9
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
Process finished with exit code 0
使用序列化(Serialization)功能来将Memento类的实例保存为文件。请修改示例程序以实现下列功能。
game.dat
文件时,以所持金钱数目为100开始游戏;如果发现 game.dat 已经存在,则以文件中所保存的状态开始游戏game.dat
文件中public class Memento implements Serializable {}
1
Caretaker
类package com.test.memento.A4;
import com.test.memento.A4.game.Gamer;
import com.test.memento.A4.game.Memento;
import java.io.*;
public class Main {
/**
* 数据保存的文件名
*/
public static final String SAVE_FILE_NAME = "game.dat";
public static void main(String[] args) {
// 最初的所持金钱数为100
Gamer gamer = new Gamer(100);
// 从文件中读取起始状态
Memento memento = loadMemento();
if (memento != null) {
System.out.println("读取上次保存存档开始游戏。");
gamer.restoreMemento(memento);
} else {
System.out.println("新游戏。");
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();
// 将实例保存至文件中
saveMemento(memento);
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println(" (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
gamer.restoreMemento(memento);
}
// 等待一段时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
/**
* 保存实例到文件中
* @param memento
*/
public static void saveMemento(Memento memento) {
try {
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(SAVE_FILE_NAME));
out.writeObject(memento);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从文件中读取实例
* @return
*/
public static Memento loadMemento() {
Memento memento = null;
try {
ObjectInput in = new ObjectInputStream(new FileInputStream(SAVE_FILE_NAME));
memento = (Memento)in.readObject();
in.close();
} catch (FileNotFoundException e) {
System.out.println(e.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return memento;
}
}
【优点】
【缺点】
【适用应用场景】
ctrl+z