文章目录
1、备忘录模式
一种状态恢复机制,以便可以回到之前的某一个特定状态。如Word的撤销操作、下棋时的悔棋等。
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
主要角色有:
- 发起人角色:记录当前时刻的内部状态信息,包含创建备忘录 + 恢复备忘录数据的方法
- 备忘录角色:发起人状态的一个备份,负责存储发起人的内部状态,并能提供内部状态数据给发起人去恢复数据
- 管理者角色:管理备份,包含保存备忘录 + 获取备忘录的方法
2、案例:游戏角色属性数值恢复
一游戏角色有生命力、攻击力、防御力三个数值,而打Boss会掉血,现要支持可恢复到打Boss前的状态。
2.1 白箱备忘录模式
即备忘录角色内部所存储的状态,对所有对象公开。
先定义发起人角色:游戏角色类(它需要备份和恢复),注意saveState和recoverState
方法
//游戏角色类
@Getter
@Setter
public class GameRole {
private int vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化状态,默认出门给100生命值
public void initState() {
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(vit, atk, def);
}
//恢复角色状态,即从备份者对象中取出属性,给当前发起者对象赋值
public void recoverState(RoleStateMemento roleStateMemento) {
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//业务方法,展示下当前发起者的属性,方便看测试效果
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
}
定义备忘者类:
//游戏状态存储类(备忘录类)
@Getter
@Setter
public class RoleStateMemento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
}
定义管理者类,保存和给外界获取备忘录对象。这里直接用get和set方法搭配一个属性简单地完成一下存储和获取。如果对象很多,可使用Map<String, 备忘录类>
//角色状态管理者类
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
测试类,看下效果:
//测试类
public class Client {
public static void main(String[] args) {
System.out.println("------------大战Boss前------------");
//大战Boss前
GameRole gameRole = new GameRole();
gameRole.initState(); //初始化生命值等,给100
gameRole.stateDisplay(); //打印生命值等属性
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); //创建管理者对象
roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); //保存备份者对象到管理者
System.out.println("------------大战Boss后------------");
//大战Boss时,损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("------------恢复之前状态------------");
//恢复之前状态
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); //从管理者获取到备份对象,给发起者去恢复
gameRole.stateDisplay();
}
}
运行:
以上:在管理者类中,可以修改备忘录对象的值,这也是白箱模式的问题所在。
2.2 黑箱备忘录模式
以上的问题,我希望:备忘录角色对象,对发起人对象是宽接口,而对管理者对象、其他对象是窄接口(权限和功能有限)。
Java中,实现双重接口(宽、窄接口)的方式是设计成内部成员类。
因此,将备忘录类设计成发起人类的内部成员类:
定义备忘录的窄接口Memento
,这是一个标识接口,因此没有定义出任何的方法
public interface Memento {
}
定义发起人类 GameRole
,并在内部定义备忘录内部类 RoleStateMemento
去实现Memento(该内部类设置为私有的),注意saveState和recoverState
方法的形参类型面向接口,直接调用没有任何方法,需要向下转型为私有的内部类。
//游戏角色类
public class GameRole {
private int vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态
public Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
//恢复角色状态
public void recoverState(Memento memento) {
向下转型
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//业务方法,展示下当前发起者的属性,方便看测试效果
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
//Getter 和 Setter
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
!!!私有的内部类,并且实现窄接口
private class RoleStateMemento implements Memento {
private int vit;
private int atk;
private int def;
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
负责人角色类 RoleStateCaretaker
能够得到的备忘录对象是以 Memento
为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容(因为无任何方法可调)
//角色状态管理者类
public class RoleStateCaretaker {
private Memento memento; //这次用Memento类型的备份者对象
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
客户端测试类:
public class Client {
public static void main(String[] args) {
System.out.println("------------大战Boss前------------");
//大战Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); //创建管理者对象
roleStateCaretaker.setMemento(gameRole.saveState()); //保存备份对象到管理者
System.out.println("------------大战Boss后------------");
//大战Boss时,损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("------------恢复之前状态------------");
//恢复之前状态
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
到此,管理者对象就不能去修改或者访问我的备份者对象的属性数据了。 因为管理者对象里是Memento类,而Memento中无任何方法。这就是黑箱!
最后,一句话,白箱变黑箱,是把备忘录的类定义在了发起者的内部,且为private,只允许本类访问。
3、总结
优点:
- 提供了状态恢复机制来恢复对象数据
- 黑箱模式下,除了发起人,其他对象均不可更改或者访问备份对象的信息
- 简化了发起人角色类,它自己不需要去保存其内部各个状态的备份,而是信息存备忘录对象后,交给管理者对象统一管理
缺点:
- 很明显,如果保存状态很多,则管理者类存储的备份者对象就很多,会占用大量内存
适用场景:
- 需要保存和恢复中间数据,如游戏中间数据的临时存档
- 需要提供一个可回滚的场景时,如撤销