目录
一、定义
二、备忘录案例分析
三、总结
一、定义
备忘录模式提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行,其定义如下:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
备忘录模式属于行为型模式,通俗来讲,备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法。其实有很多备忘录模式的例子,像我们平常使用的开发工具的Ctrl + Z就提供了一种撤销的功能、事务回滚等都是备忘录模式的范例。
备忘录模式的通用类图如下:
角色分析:
- Original发起人角色: 记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据;
- Memento备忘录角色:负责存储发起人角色的内部状态,在需要的时候提供发起人需要的内部状态;
- Caretaker备忘录管理者角色:管理多个备忘录对象,通常用集合进行保存,建议使用Map进行保存,因为可以保存多个原始对象不同时间的备忘录对象;
二、备忘录案例分析
经常玩游戏的小伙伴都知道,有些游戏角色在死亡之后,还能立马复活,恢复到死亡之前的状态,接下来我们就是用备忘录模式来模拟这一场景。先来看下整体的类图:
首先我们先创建一个发起人角色,其提供了创建备忘录和恢复备忘录的方法:
/**
* 发起人角色:英雄
*/
public class GameHero {
/**
* 攻击力
*/
private int aggressivity;
/**
* 防御力
*/
private int defense;
public GameHero(int aggressivity, int defense) {
this.aggressivity = aggressivity;
this.defense = defense;
}
public int getAggressivity() {
return aggressivity;
}
public void setAggressivity(int aggressivity) {
this.aggressivity = aggressivity;
}
public int getDefense() {
return defense;
}
public void setDefense(int defense) {
this.defense = defense;
}
/**
* 保存当前的状态
* 实质上就是创建一个备忘录对象,可以简单理解为创建一个副本
*
* @return
*/
public Memento saveMemento() {
return new Memento(this.aggressivity, this.defense);
}
/**
* 恢复之前的状态
* 从之前的副本中恢复过来
*
* @param memento
*/
public void restoreMemento(Memento memento) {
this.aggressivity = memento.getAggressivity();
this.defense = memento.getDefense();
}
@Override
public String toString() {
return "GameHero{" +
"攻击力=" + aggressivity +
", 防御力=" + defense +
'}';
}
}
接着定义我们的备忘录角色,它具有和发起人相类似的属性:
/**
* 备忘录角色: 通常具有跟发起人角色相同或者少一些属性
*/
public class Memento {
/**
* 攻击力
*/
private int aggressivity;
/**
* 防御力
*/
private int defense;
public Memento(int aggressivity, int defense) {
this.aggressivity = aggressivity;
this.defense = defense;
}
public int getAggressivity() {
return aggressivity;
}
public void setAggressivity(int aggressivity) {
this.aggressivity = aggressivity;
}
public int getDefense() {
return defense;
}
public void setDefense(int defense) {
this.defense = defense;
}
}
最后需要一个备忘录的管理者:这里我们使用栈结构保存备忘录,具体代码如下:
/**
* 管理者角色
*/
public class Caretaker {
/**
* 使用栈结构保存备忘录
*/
private Stack<Memento> mementos = new Stack<>();
private int index = -1;
public void save(Memento memento) {
while (index < mementos.size() - 1) {
//将栈中在当年索引index之上的所有元素出栈
mementos.pop();
}
mementos.push(memento);
index++;
}
public Memento reserve() {
index = index >= 0 ? index - 1 : 0;
return mementos.get(index);
}
public Memento redoReserve() {
index = index < (mementos.size() - 1) ? index + 1 : (mementos.size() - 1);
return mementos.get(index);
}
}
我们写一个场景类测试一下:
/**
* 场景类
*/
public class Client {
private static Caretaker caretaker = new Caretaker();
public static void main(String[] args) {
GameHero gameHero = new GameHero(10, 10);
saveCurrentState(gameHero);
gameHero = new GameHero(20, 20);
saveCurrentState(gameHero);
gameHero = new GameHero(30, 30);
saveCurrentState(gameHero);
gameHero = new GameHero(40, 40);
saveCurrentState(gameHero);
reserveState(gameHero);
reserveState(gameHero);
gameHero = new GameHero(50, 50);
saveCurrentState(gameHero);
redoReserveState(gameHero);
}
private static void saveCurrentState(GameHero gameHero) {
System.out.println("******保存状态******");
//生成一个副本
Memento memento = gameHero.saveMemento();
//保存备忘录到栈中
caretaker.save(memento);
System.out.println(gameHero);
}
private static void reserveState(GameHero gameHero) {
System.out.println("******恢复状态******");
gameHero.restoreMemento(caretaker.reserve());
System.out.println(gameHero);
}
private static void redoReserveState(GameHero gameHero) {
System.out.println("******撤销恢复状态******");
gameHero.restoreMemento(caretaker.redoReserve());
System.out.println(gameHero);
}
}
程序运行结果:
******保存状态******
GameHero{攻击力=10, 防御力=10}
******保存状态******
GameHero{攻击力=20, 防御力=20}
******保存状态******
GameHero{攻击力=30, 防御力=30}
******保存状态******
GameHero{攻击力=40, 防御力=40}
******恢复状态******
GameHero{攻击力=30, 防御力=30}
******恢复状态******
GameHero{攻击力=20, 防御力=20}
******保存状态******
GameHero{攻击力=50, 防御力=50}
******撤销恢复状态******
GameHero{攻击力=50, 防御力=50}
可以看到,通过备忘录模式,我们实现了保存和撤销某个对象状态的功能。
三、总结
优点:
- 提供了一种方式可以撤销回退到历史某个状态,保留了原始对象的状态信息;
缺点:
- 如果原始对象的属性太多的话,那么备忘录对象也会有很多属性,这样就会创建比较多这样的对象,每一次保存原始对象状态都会耗费很多内存;
使用场景:
- 需要保存和恢复数据的相关状态场景;
- 提供一个可回滚的操作;
- 需要监控的副本场景中;
- 数据库连接的事务管理;
注意事项:
- 备忘录的生命周期
备忘录创建出来就要在最近的代码中使用,要主动管理它的生命周期,建议就要使用,不适用就要立即删除其引用,等待垃圾回收器对它的回收。
- 备忘录的性能
不要在频繁建立备份的场景中使用备忘录模式,比如一个for循环中,原因有二:一是控制不了备忘录建立的对象数量;二是大对象的建立是要消耗资源的,系统的性能需要考虑。