- 备忘录模式 Memento Pattern
备忘录模式又称为快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
特征:“后悔药”
备忘录模式的主要角色如下:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
1.1 “ 白箱 ” 备忘录模式
下面就以游戏打怪为简单的例子进行代码实现(下面“黑箱”同这个例子):
备忘录角色对任何对象都提供一个宽接口,备忘录角色的内部所存储的状态就对所有对象公开。
// 游戏角色类
@Data
public class GameRole {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
// 初始化状态
public void init() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 战斗到0
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
// 保存角色状态
public RoleStateMemento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}
// 回复角色状态
public void recoverState(RoleStateMemento roleStateMemento) {
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
// 展示状态
public void showState() {
System.out.println("角色生命力:" + this.vit);
System.out.println("角色攻击力:" + this.atk);
System.out.println("角色防御力:" + this.def);
}
}
// 游戏状态存储类(备忘录类)
@Data
@AllArgsConstructor
public class RoleStateMemento {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
}
// 角色状态管理者类
@Data
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;
}
// 测试结果
public static void main(String[] args){
System.out.println("===========打boss前状态===========");
GameRole gameRole = new GameRole();
gameRole.init();
gameRole.showState();
// 保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("===========打boss后状态===========");
gameRole.fight();
gameRole.showState();
System.out.println("===========恢复状态===========");
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.showState();
// ===========打boss前状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
// ===========打boss后状态===========
// 角色生命力:0
// 角色攻击力:0
// 角色防御力:0
// ===========恢复状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
}
“白箱”备忘录模式是破坏封装性的,但是通过程序员自律,同样可以在一定程度上实现大部分的用意。
1.2 “ 黑箱 ” 备忘录模式
备忘录角色对发起人对象提供了一个宽接口,而为其他对象提供一个窄接口,在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将RoleStateMemento设为GameRole的内部类,从而将RoleStateMemento对象封装在GameRole 里面;在外面提供一个标识接口Memento给RoleStateCaretaker及其他对象使用。这样GameRole类看到的是RoleStateMemento所有的接口,而RoleStateCaretaker及其他对象看到的仅仅是标识接口Memento所暴露出来的接口,从而维护了封装型。
// 窄接口,标识接口
public interface Memento {
}
// 角色状态管理者类
@Data
public class RoleStateCaretaker {
private Memento memento;
}
// 游戏角色类
@Data
public class GameRole {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
// 初始化状态
public void init() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 战斗到0
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
// 保存角色状态
public RoleStateMemento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}
// 回复角色状态
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
// 展示状态
public void showState() {
System.out.println("角色生命力:" + this.vit);
System.out.println("角色攻击力:" + this.atk);
System.out.println("角色防御力:" + this.def);
}
// 备忘录内部类
@Data
@AllArgsConstructor
private class RoleStateMemento implements Memento {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
}
}
// 测试结果
public static void main(String[] args){
System.out.println("===========打boss前状态===========");
GameRole gameRole = new GameRole();
gameRole.init();
gameRole.showState();
// 保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("===========打boss后状态===========");
gameRole.fight();
gameRole.showState();
System.out.println("===========恢复状态===========");
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.showState();
// ===========打boss前状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
// ===========打boss后状态===========
// 角色生命力:0
// 角色攻击力:0
// 角色防御力:0
// ===========恢复状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
}
1.3 总结
适用场景
- 需要保存历史快照的场景。
-希望在对象之外保存状态,且除了自己其他类对象无法访问状态保存具体内容。
优点:
-简化发起人实体类职责,隔离状态存储与获取,实现了信息的封装,客户端无需关心状态的保存细节。
-提供状态回滚功能。
缺点:
- 消耗资源:如果需要保存的状态过多时,每一次保存都会消耗很多内存