30. 备忘录模式
定义
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。别名为Token。
通俗理解
电脑的Ctrl+Z,相信大家用得不少,按下这个键,就能够回退到上一步了,不经感叹,如果人生也有Ctrl+Z就好了,如果有,我一定会。好了😫,人生没有后悔药,也没有Ctrl+Z,过去就让他过去吧。
程序上有呀😎!备忘录模式就是实现Ctrl+Z的过程,保存下对象的状态,然后调用回退方法的时候,就可以把对象的状态回退到上一步。当然这并不是备忘录模式的全部,除了保存对象状态外,还需要的是,不破坏封装的转下,捕获对象的状态。这个意思是,你对象本身,就不要去保存自己的状态了,而是交给外部的类来进行保存,就像照相一样,你不能把自己压成一张照片来保存当前的状态,而是要通过照相机📷,把自己保存下来。
示例
每一个人在成长过程都长高,体重也会长,这次的业务就是保存某几个年龄时候的身高与体重。
渣渣程序
人类
public class Human {
private List<Human> saveLocal = new ArrayList<>(16);
private int age;
private int high;
private int weight;
// 构造器,getter,setter,toString方法省略
public void save() {
this.saveLocal.add(new Human(age, high, weight));
}
public void restore(int i) {
if(i < saveLocal.size()) {
Human human = saveLocal.get(i);
this.age = human.getAge();
this.high = human.getHigh();
this.weight = human.getWeight();
return;
}
System.out.println("没有保存任何副本");
}
}
主程序入口
public class Main {
public static void main(String[] args) {
Human human = new Human(1,12,12);
human.save();
human.growUp(2,23,23);
human.save();
System.out.println(human);
human.restore(0);
System.out.println(human);
}
}
//Human{age=2, high=23, weight=23}
//Human{age=1, high=12, weight=12}
基本上实现了保存状态的功能了,但是这种写法,一是不符合单一职责原则,在对象里面维护了保存这个对象的list列表,就像是把自己“拍”成照片一样,本身维护这个状态不应该是对象本身去弄的,涉及到职责的问题。二是,备忘录模式根本就不是这么写,要明白,所有的设计模式都是封装一层,加类。好了,第二条是我扯的,这是规则,我们就按这个规则来写我们的备忘录吧。
优化
白箱实现
人类
public class Human {
private int age;
private int high;
private int weight;
public void growUp(int age, int high, int weight) {
this.age = age;
this.high = high;
this.weight = weight;
}
public HumanMemento save() {
return new HumanMemento(this.age, this.high, this.weight);
}
public void restore(HumanMemento memento) {
this.age = memento.getAge();
this.high = memento.getHigh();
this.weight = memento.getWeight();
}
}
人类备忘录
public class HumanMemento {
private int age;
private int high;
private int weight;
// 构造器,setter,getter方法省略
}
负责人
public class MementoCaretaker {
private List<HumanMemento> mementos = new ArrayList<>(16);
public HumanMemento getMemento(int i) {
return mementos.get(i);
}
public void setMemento(HumanMemento memento) {
mementos.add(memento);
}
}
程序入口
public class Main {
private static MementoCaretaker caretaker = new MementoCaretaker();
public static void main(String[] args) {
Human human = new Human(1,12,12);
caretaker.setMemento(human.save());
human.growUp(2,23,23);
caretaker.setMemento(human.save());
System.out.println(human);
human.restore(caretaker.getMemento(0));
System.out.println(human);
human.restore(new HumanMemento(1,1,1));
System.out.println(human);
}
}
//Human{age=2, high=23, weight=23}
//Human{age=1, high=12, weight=12}
//Human{age=1, high=1, weight=1}
完了?没有,上面程序中,我创建了一个新的HumanMemento
并把他设置进restore
方法中,那么我就能够把Human
变成1岁了,合理么?显然不合理,因为这1岁根本就不是Human
备份过的状态。
怎么办?也许想到了private
的构造方法,但是,把所有的方法够设置成private
之后,面临的问题就是Human
访问不了HumanMemento
,既然访问不了HumanMemento
,谈什么备份呢?
幸好Java有一个内部类的概念,对于内部类,只需要知道一点就够了,外部不能访问私有内部类,通过内部类,我们就可以创造出一个安全的备忘录模式。
黑箱实现
备忘录接口
public interface IMemento {
}
原发器 人类
public class Human {
private int age;
private int high;
private int weight;
public IMemento save() {
return new HumanMemento(this.age, this.high, this.weight);
}
public void restore(IMemento memento) {
this.age = ((HumanMemento)memento).getAge();
this.high = ((HumanMemento)memento).getHigh();
this.weight = ((HumanMemento)memento).getWeight();
}
// 构造器、setter、getter省略
private class HumanMemento implements IMemento {
private int age;
private int high;
private int weight;
// 构造器、setter、getter省略
}
负责人类
public class MementoCaretaker {
private List<IMemento> mementos = new ArrayList<>(16);
public IMemento getMemento(int i) {
return mementos.get(i);
}
public void setMemento(IMemento memento) {
mementos.add(memento);
}
}
程序入口
public class Main {
private static MementoCaretaker caretaker = new MementoCaretaker();
public static void main(String[] args) {
Human human = new Human(1,12,12);
caretaker.setMemento(human.save());
human.growUp(2,23,23);
caretaker.setMemento(human.save());
System.out.println(human);
human.restore(caretaker.getMemento(0));
System.out.println(human);
}
}
//Human{age=2, high=23, weight=23}
//Human{age=1, high=12, weight=12}
基本完美,不完美的地方就是,还是能够创建一个对象实现IMemento
,然后把这个对象作为入参。
在圈子里玩!!!
优点
- 保存对象状态,可以在某个时期的时候回退到某个状态;
- 与原发器(Human)解耦,简化原发器的代码。
缺点
- 保存状态需要创建新的类,消耗资源。
应用场景
- 需要保存历史状态的。
- 和命令模式接口,在命令模式中undo的。
实例
Web中Session、Cookie。
程序
吐槽
就是一个List(历史)列表,保存对象状态。