备忘录模式:备忘录模式提供了一种状态恢复机制,使得用户可以很方便的回到一个特定的历史步骤。
现实场景是比较多的,比如文档编辑中的ctrl+z,或者代码提交中的回滚,都是这种概念。
简单的来说,就是对于一个对象,我先在某个地方保存它当前的一个状态,之后当它经历过一些操作之后想恢复到当初的那个状态,那再把之前保存的那个状态拿出来就可以恢复了。
这里面涉及到三种角色:发起人角色(也就是一个主体对象,他的状态会发生变化,之后又可能要改变),备忘录角色(负责记录发起人的状态),管理者角色(对备忘录进行管理,只能保存和获取,不能修改)
这里有两种方式:白箱备忘录,黑箱备忘录
举个实际的例子,手机的初始化也是一种状态恢复,假设初始的时候手机有一系类的参数设置,经过一段时间的使用之后,这些参数都发生了变化,此时用户想把手机的其他数据都清理掉,让它回到最初的状态,我们用备忘录模式来实现一下。
首先看简单的白箱备忘录:
发起人:Phone
public class Phone {
private Integer memory;
private Integer disk;
private String system;
//初始状态
public Phone() {
this.memory = 128;
this.disk = 500;
this.system = "andriod";
}
// 保存初始状态,这里需要一个备忘录对象
public StateMemento saveState(){
// 在备忘录中记录这个phone的初始状态
return new StateMemento(memory, disk, system);
}
// 恢复状态,从备忘录中获取之前保存的值
public void recover(StateMemento stateMemento){
this.memory = stateMemento.getMemory();
this.disk = stateMemento.getDisk();
this.system = stateMemento.getSystem();
}
public Integer getMemory() {
return memory;
}
public void setMemory(Integer memory) {
this.memory = memory;
}
public Integer getDisk() {
return disk;
}
public void setDisk(Integer disk) {
this.disk = disk;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
@Override
public String toString() {
return "Phone{" +
"memory=" + memory +
", disk=" + disk +
", system='" + system + '\'' +
'}';
}
}
备忘录:
public class StateMemento {
private Integer memory;
private Integer disk;
private String system;
public StateMemento(Integer memory, Integer disk, String system) {
this.memory = memory;
this.disk = disk;
this.system = system;
}
public Integer getMemory() {
return memory;
}
public void setMemory(Integer memory) {
this.memory = memory;
}
public Integer getDisk() {
return disk;
}
public void setDisk(Integer disk) {
this.disk = disk;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
}
管理者:
public class MementoManager {
// 管理中保存者备忘录对象,发起人保存的时候把备忘录保存到这里,等要恢复状态的时候再从这里获取之前的备忘录
private StateMemento stateMemento;
public StateMemento getStateMemento() {
return stateMemento;
}
public void setStateMemento(StateMemento stateMemento) {
this.stateMemento = stateMemento;
}
}
测试:
public class Client {
public static void main(String[] args) {
Phone huawei = new Phone();
System.out.println("-------初始状态------");
System.out.println(huawei);
// 保存初始状态
MementoManager manager = new MementoManager();
manager.setStateMemento(huawei.saveState());
System.out.println("------开始使用手机-------");
System.out.println("---消耗内存:100G");
huawei.setMemory(huawei.getMemory() - 100);
System.out.println("----消耗磁盘200G");
huawei.setDisk(huawei.getDisk() - 200);
System.out.println("重装个系统吧,鸿蒙不错");
huawei.setSystem("鸿蒙系统");
System.out.println("-----当前状态----------");
System.out.println(huawei.toString());
System.out.println("-------恢复状态--------");
huawei.recover(manager.getStateMemento());
System.out.println(huawei.toString());
}
}
白箱备忘录有一个安全性问题,那就是管理者是拥有备忘录的,那么这样那就可能会对备忘录的内容进行修改,这样就没办法保证保存的备忘录的数据准确性。
黑箱备忘录:这是对白箱备忘录的改进,我们只定义个备忘录角色的接口,便于管理者保存,但是具体的备忘录对象定义在发起人内部,这样管理者无法获取备忘录对象对其内部属性进行修改。
发起人:
public class Phone {
private Integer memory;
private Integer disk;
private String system;
//初始状态
public Phone() {
this.memory = 128;
this.disk = 500;
this.system = "andriod";
}
// 保存初始状态,这里需要一个备忘录对象
public Memento saveState(){
// 在备忘录中记录这个phone的初始状态
return new StateMemento(memory, disk, system);
}
// 恢复状态,从备忘录中获取之前保存的值
public void recover(Memento memento){
StateMemento stateMemento = (StateMemento) memento;
this.memory = stateMemento.getMemory();
this.disk = stateMemento.getDisk();
this.system = stateMemento.getSystem();
}
// 备忘录使用内部类的方式,这样可以防止外部获取对象修改
private class StateMemento implements Memento{
private Integer memory;
private Integer disk;
private String system;
public StateMemento(Integer memory, Integer disk, String system) {
this.memory = memory;
this.disk = disk;
this.system = system;
}
public Integer getMemory() {
return memory;
}
public void setMemory(Integer memory) {
this.memory = memory;
}
public Integer getDisk() {
return disk;
}
public void setDisk(Integer disk) {
this.disk = disk;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
}
public Integer getMemory() {
return memory;
}
public void setMemory(Integer memory) {
this.memory = memory;
}
public Integer getDisk() {
return disk;
}
public void setDisk(Integer disk) {
this.disk = disk;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
@Override
public String toString() {
return "Phone{" +
"memory=" + memory +
", disk=" + disk +
", system='" + system + '\'' +
'}';
}
}
备忘录:
// 定义这个接口只是为了让管理者可以管理备忘录,实现类放在发起人内部,这样就可以防止管理者篡改其内部数据
public interface Memento {
}
管理者:
public class MementoManager {
// 管理中保存者备忘录对象,发起人保存的时候把备忘录保存到这里,等要恢复状态的时候再从这里获取之前的备忘录
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试:
public class Client {
public static void main(String[] args) {
Phone huawei = new Phone();
System.out.println("-------初始状态------");
System.out.println(huawei);
// 保存初始状态
MementoManager manager = new MementoManager();
manager.setMemento(huawei.saveState());
System.out.println("------开始使用手机-------");
System.out.println("---消耗内存:88G");
huawei.setMemory(huawei.getMemory() - 88);
System.out.println("----消耗磁盘200G");
huawei.setDisk(huawei.getDisk() - 55);
System.out.println("重装个系统吧,鸿蒙");
huawei.setSystem("鸿蒙");
System.out.println("-----当前状态----------");
System.out.println(huawei.toString());
System.out.println("-------恢复状态--------");
huawei.recover(manager.getMemento());
System.out.println(huawei.toString());
}
}
备忘录模式从概念上比较容易理解,实现上也不复杂。感觉似乎也挺有用,而且这里有一个扩展点,这里的例子只是保存了一个节点的状态,实际上我们可能需要保存很多个节点的状态,比如我们代码提交记录,就需要保存多个提交节点的记录。这样我们可以改造一下,比如这个备忘录对象我们可以用Map的格式,给每一个状态一个key,比如当前时间或者生成一个唯一标识,value就是要保存的数据,这样当我们想要恢复的时候,就可以从map中获取对应时间节点的数据。
但是备忘录模式有一个问题,我们可以看到备忘录的对象都是在内存中保存的,如果要保存的数据比较多,那么对内存的消耗就会比较大,所以使用前需要评估数据的量。