备忘录设计模式
1. 简单介绍
备忘录模式(Memento)是一种行为型设计模式
,该模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
2. 使用场景
- 需要保存一个对象在某一个时刻的状态或部分状态
- 一个对象需要暴露自己的内部状态,而又不希望外界直接对其内部状态进行访问。此时外界可通过备忘录模式中的Caretaker来间接访问该对象的内部状态
3. 场景举例
当我们在玩单机游戏时,为了避免每次开机重头开始,可以将游戏当前状态进行存档,之后再次进入游戏,只需读取存档即可恢复到存档时的游戏状态。这便是备忘录模式的典型应用场景,即备忘某一时刻游戏(或实例对象)状态。对于单机游戏来说,游戏的状态需要备忘到硬盘上,以免关机丢失。
4. UML类图
5. 具体实现
方式一:通过实例对象进行Originator进行状态备忘
描述
- 背景:英雄联盟游戏中的艾希(Ashe)角色的状态保存与恢复
- Ashe:英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
- Memento:用于保存艾希的状态
- LOLCaretaker:英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
- Client:经常崩溃的英雄联盟客户端
代码实现
Ashe.java
/**
* 京东商城,充当ObjectStruct角色
*/
/**
* 英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
*/
public class Ashe {
/**
* 血量,需要备忘保存
*/
private Integer HP;
/**
* 蓝量,需要备忘保存
*/
private Integer blueAmount;
/**
* 攻击速度,需要备忘保存
*/
private Double attackSpeed;
/**
* 攻击力,需要备忘保存
*/
private Integer attackPower;
/**
* 固定属性,不需要备忘保存
*/
private String roleName = "Ashe";
public Memento saveStatus() {
return new Memento(HP, blueAmount, attackSpeed, attackPower);
}
public void restoreFrom(Memento memento) {
this.HP = memento.getHP();
this.blueAmount = memento.getBlueAmount();
this.attackSpeed = memento.getAttackSpeed();
this.attackPower = memento.getAttackPower();
}
// 省略Getter、Seeter和toString方法
}
Element.java
/**
* Memento,用于保存艾希的状态
*/
public class Memento {
/**
* 血量
*/
private Integer HP;
/**
* 蓝量
*/
private Integer blueAmount;
/**
* 攻击速度
*/
private Double attackSpeed;
/**
* 攻击力
*/
private Integer attackPower;
public Memento(Integer HP, Integer blueAmount, Double attackSpeed, Integer attackPower) {
this.HP = HP;
this.blueAmount = blueAmount;
this.attackSpeed = attackSpeed;
this.attackPower = attackPower;
}
// 省略Getter、Seeter方法
}
LOLCaretaker.java
/**
* 英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
*/
public class LOLCaretaker {
private static Map<String, Memento> roleMemento = new HashMap<>();
public static void put(String roleName, Memento memento) {
roleMemento.put(roleName, memento);
}
public static Memento get(String roleName) {
return roleMemento.get(roleName);
}
}
Client.java
/**
* 经常崩溃的英雄联盟客户端
*/
public class LOLClient {
public static void main(String[] args) {
// 欢迎来到召唤师,碾碎他们
// 游戏开始,设置艾希的初始化状态
Ashe ashe = new Ashe();
ashe.setHP(642);
ashe.setBlueAmount(600);
ashe.setAttackPower(120);
ashe.setAttackSpeed(1.2D);
System.out.println(ashe.toString());
// 进行备忘
LOLCaretaker.put(ashe.getRoleName(), ashe.saveStatus());
// 经过一轮SOLO后,艾希状态很差
ashe.setHP(50);
ashe.setBlueAmount(100);
ashe.setAttackPower(120);
ashe.setAttackSpeed(1.2D);
System.out.println(ashe.toString());
// 开挂,恢复初始状态
ashe.restoreFrom(LOLCaretaker.get(ashe.getRoleName()));
System.out.println(ashe.toString());
}
}
方式二:通过文件对Originator进行状态备忘
描述
- 背景:英雄联盟游戏中的艾希(Ashe)角色的状态保存与恢复,与方式以不同的是,本次通过文件的方式进行角色状态的保存。其他均和方式一相同
代码实现
Ashe.java
/**
* 英雄联盟游戏角色艾希,充当备忘录模式中的Originator角色
*/
public class Ashe implements Serializable {
/**
* 血量,需要备忘保存
*/
private Integer HP;
/**
* 蓝量,需要备忘保存
*/
private Integer blueAmount;
/**
* 攻击速度,需要备忘保存
*/
private Double attackSpeed;
/**
* 攻击力,需要备忘保存
*/
private Integer attackPower;
/**
* 固定属性,不需要备忘保存
*/
private String roleName = "Ashe";
public Memento saveStatus() throws Exception {
return new Memento(this);
}
public void restoreFrom(Memento memento) {
this.HP = memento.getHP();
this.blueAmount = memento.getBlueAmount();
this.attackSpeed = memento.getAttackSpeed();
this.attackPower = memento.getAttackPower();
}
// 省略Getter、Seeter和toString方法
}
Memento.java
/**
* Memento,用于保存艾希的状态
*/
public class Memento implements Serializable {
private static final String FILE_PATH = "D:\\ashe";
/**
* 血量
*/
private Integer HP;
/**
* 蓝量
*/
private Integer blueAmount;
/**
* 攻击速度
*/
private Double attackSpeed;
/**
* 攻击力
*/
private Integer attackPower;
// 将需要备忘的状态保存在硬盘文件上
public Memento(Ashe ashe) throws Exception {
this.HP = ashe.getHP();
this.blueAmount = ashe.getBlueAmount();
this.attackSpeed = ashe.getAttackSpeed();
this.attackPower = ashe.getAttackPower();
File file = new File(FILE_PATH);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(this);
oos.flush();
}
// 从硬盘文件上加载保存过的状态
public static Memento loadFromFile() throws Exception {
File file = new File(FILE_PATH);
if (!file.exists()) {
return null;
}
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
return (Memento) ois.readObject();
}
LOLCaretaker.java
/**
* 英雄联盟角色状态保存负责人,充当备忘录模式中的Caretaker角色
*/
public class LOLCaretaker {
private static Map<String, Memento> roleMemento = new HashMap<>();
public static void put(String roleName, Memento memento) {
roleMemento.put(roleName, memento);
}
public static Memento get(String roleName) {
return roleMemento.get(roleName);
}
}
Client.java
/**
* 经常崩溃的英雄联盟客户端
*/
public class LOLClient {
public static void main(String[] args) {
// 欢迎来到召唤师,碾碎他们
// 游戏开始,设置艾希的初始化状态
Ashe ashe = new Ashe();
ashe.setHP(642);
ashe.setBlueAmount(600);
ashe.setAttackPower(120);
ashe.setAttackSpeed(1.2D);
System.out.println(ashe.toString());
// 进行备忘
LOLCaretaker.put(ashe.getRoleName(), ashe.saveStatus());
// 经过一轮SOLO后,艾希状态很差
ashe.setHP(50);
ashe.setBlueAmount(100);
ashe.setAttackPower(120);
ashe.setAttackSpeed(1.2D);
System.out.println(ashe.toString());
// 开挂,恢复初始状态
ashe.restoreFrom(LOLCaretaker.get(ashe.getRoleName()));
System.out.println(ashe.toString());
}
}
7. 源码展示
Log4j
开源日志框架中使用了备忘录模式。与本文所讲解的标准的备忘录模式相比,
Log4j
中使用备忘录模式,显得更为简洁,却也能达到备忘的作用。
Log4j
中
Log4jLogEvent
类,充当
Originator
角色,它在特定状态下需要对某些状态进行备忘。其实现方式是通过
Builder
模式,建造一个
Log4jLogEvent
实例(含有需要备忘的状态),即
Memento
实例。并通过
AbstractJacksonLayout
类对该实例进行打印操作。其中源码如下:
RingBufferLogEvent.java
/**
* 对应备忘录模式中的Originator角色
*/
public class Log4jLogEvent implements LogEvent {
/**
* 需要进行备忘的状态值
* /
private final String loggerFqcn;
private final Marker marker;
private final Level level;
private final String loggerName;
private Message message;
// ......
/**
* 创建备忘实例,其中LogEvent是Log4jLogEvent所实现的接口
* 故Log4jLogEvent使用的备忘录模式,与标准的备忘录模式相比,没有Memento这一角色
*/
public LogEvent createMemento() {
return new Log4jLogEvent.Builder(this).build();
}
AbstractJacksonLayout.java
abstract class AbstractJacksonLayout extends AbstractStringLayout {
/**
* 当event是LogEvent实例时,调用createMemento方法,创建event的Memento对象
* 返回的Memento对象类型依然是LogEvent,故该类即是Originator也是Memento
*/
private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event);
}
/**
* 参数event对应备忘录模式中的Memento
* 该方法将含有备忘状态信息的event进行打印
*/
public void toSerializable(final LogEvent event, final Writer writer) {
// convertMutableToLog4jEvent(event)返回备忘对象
// writeValue,对备忘对象进行打印
objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event)));
writer.write(eol);
if (includeNullDelimiter) {
writer.write('\0');
}
markEvent();
}
}