【行为型模式五】备忘录模式
一、把计算器的价格和状态备份
在状态模式中,我们实现了根据生产日期改变计算器的状态从而改变价格,如果需要回到之前的状态和价格,可以使用备忘录模式将状态和价格存储下来,类似游戏中的存档。
二、备忘录模式
备忘录模式(memento),在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
2.1 备忘录模式的角色
使用备忘录模式保存计算器之前状态和价格的类图:
-
Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。例如Calculator类。
-
Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。例如Memento类。
-
Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在此类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。例如MenmentoCaretaker类。
备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计。
就是把要保存的细节给封装在Menento类中,哪一天要更改保存的细节,也不会影响到客户端。
Memento模式比较适合用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,通过Memento来恢复到前一状态。使用备忘录模式可以把复杂对象的内部信息对其他对象屏蔽起来,从而可以恰当地保持封装的边界。
命令模式也有实现类似撤销的功能,如果在某个系统中使用命令模式,需要使用撤销某个命令的功能,则可使用备忘录模式来存储命令的上一状态。
三、使用备忘录模式保存计算器状态和价格
3.1 计算器类
在计算器类中,有个backup方法用于实例化一个备忘录对象,有个recover方法用于将保存的备忘录对象的属性赋值给计算器类的对应属性。
public class Calculator {
private Date productionDate;
private int price;
private State state;
public Calculator() {
this.state = new NewProductState();
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public void setPriceByState(int basePrice) {
state.setPrice(this, basePrice);
}
public Date getProductionDate() {
return productionDate;
}
public void setProductionDate(Date productionDate) {
this.productionDate = productionDate;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public Memento backup() {
return new Memento(state, price);
}
public void recover(Memento memento) {
state = memento.getState();
price = memento.getPrice();
}
}
3.2 备忘录类
用于保存计算器类中需要保存的属性。
public class Memento {
private State state;
private int price;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Memento(State state, int price) {
this.state = state;
this.price = price;
}
}
3.3 负责人类
在负责人类中使用栈来存储之前已经保存的备忘录对象。
public class MementoCaretaker {
private Stack<Memento> mementoStack = new Stack<>();
public Memento getMemento() {
return mementoStack.pop();
}
public void setMemento(Memento memento) {
mementoStack.push(memento);
}
}
3.4 状态类
见状态模式一文。
3.5 客户端类
在客户端中使用MementoCaretaker对象来保存和恢复Calculator的状态和价格。
public class Client {
/**
* 使用备忘录模式记录计算器的价格和状态
*/
public static void backupByMemento() {
MementoCaretaker mementoCaretaker = new MementoCaretaker();
Calculator calculator = new Calculator();
//第一次更改状态和价格后备份
calculator.setState(new NewProductState());
calculator.setPrice(10);
mementoCaretaker.setMemento(calculator.backup());
//第二次更改状态和价格后备份
calculator.setState(new OldProductState());
calculator.setPrice(9);
mementoCaretaker.setMemento(calculator.backup());
//第三次更改状态和价格
calculator.setState(new DeadProductState());
calculator.setPrice(8);
System.out.println(calculator.getPrice());//现在是8
calculator.recover(mementoCaretaker.getMemento());//恢复备份
System.out.println(calculator.getPrice());//现在是9
calculator.recover(mementoCaretaker.getMemento());//恢复备份
System.out.println(calculator.getPrice());//现在是10
}
public static void main(String[] args) {
backupByMemento();
}
}
- 运行结果
8
9
10
四、备忘录模式总结
4.1 优点
-
它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
-
备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
4.2 缺点
- 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
五、备忘录模式适用场景
-
保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
-
防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
-
浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退到上一次的页面,然后也可以点击右箭头重新回到当前页面
-
数据库备份与还原:一般的数据库都支持备份与还原操作,备份即将当前已有的数据或者记录保留,还原即将已经保留的数据恢复到对应的表中
-
编辑器撤销与重做:在编辑器上编辑文字,写错时可以按快捷键 Ctrl + z 撤销,撤销后可以按 Ctrl + y 重做
-
虚拟机生成快照与恢复:虚拟机可以生成一个快照,当虚拟机发生错误时可以恢复到快照的样子
-
Git版本管理:Git是最常见的版本管理软件,每提交一个新版本,实际上Git就会把它们自动串成一条时间线,每个版本都有一个版本号,使用 git reset --hard 版本号 即可回到指定的版本,让代码时空穿梭回到过去某个历史时刻
-
棋牌游戏悔棋:在棋牌游戏中,有时下快了可以悔棋,回退到上一步重新下