前言
温故而知新
先复习学习到的行为型模式:
- 模板方法模式:定义算法、流程的骨架,特定的步骤延迟到子类具体实现(情况过程:点单-》吃-》买单,具体吃什么由子类负责实现)
- 命令模式:将命令封装成对象,命令执行者聚合到命令对象中,命令请求者调用命令对象即可完成命令,使执行者与请求者解耦合(遥控器按钮是一个个命令对象,遥控器只需按下按钮即可完成开关灯)
- 访问者模式:将施加于对象结构中元素的操作分割开,封装成访问者,对象结构提供访问者操作元素的接口,即访问者改变不需修改对象结构(购物车是一个对象结构,里面存放着很多商品元素对象,顾客可以查看商品的质量,收银员可以查看商品的价格,不会影响到购物车)
- 迭代器模式:将聚合对象的存储数据对象和遍历功能分隔,遍历功能封装成迭代器,聚合对象以工厂方法创建对应的迭代器(Linux目录通过迭代器遍历输出)
- 观察者模式:定义对象间的一对多的依赖关系,当对象状态改变时,自动通知所有依赖对象并自动更新数据(气象局的天气数据改变,相关app上的天气信息也会改变)
- 中介者模式:用中介对象来封装其他对象间的交互引用,使得对象通过中介者可以间接访问别的对象,对象间松散耦合,没有显示的引用(QQ用户将消息发给服务器,再由服务器转发给对应的用户,用户间没有直接关联也可以相互通信)
接下来,是备忘录模式
现实中的问题
现实中没有后悔药,但是计算机应用可以有后悔药
各种软件都可以“撤销”,很多游戏都有存档功能
如何实现存档,撤销,这就是备忘录模式
备忘录模式
什么是备忘录模式?
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token
为什么要备忘录模式?
在应用软件的开发过程中,很多时候我们都需要记录一个对象的内部状态
在具体实现过程中,为了允许用户取消不确定的操作或从错误中恢复过来,需要实现备份点和撤销机制,而要实现这些机制,必须事先将状态信息保存在某处,这样才能将对象恢复到它们原先的状态
通过备忘录模式可以使系统恢复到某一特定的历史状态
命令模式的撤销功能
还记得在前面命令模式的时候,可以实现撤销功能
当时是怎么实现的?
因为命令是一个个对象,可以把前一个执行对象记录下来,勉强完成撤销功能,只能往后撤销一步
可以对比一下备忘录模式
模式结构
模式角色:
- 发起人(Originator)角色:通过备忘录角色记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
模式实现案例
简单实现一下备忘录的功能
package com.company.Behavioral.Memento;
import java.util.ArrayList;
import java.util.List;
//发起人
class Originator {
//状态
private String state;
public Originator(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创建当前状态的备忘录角色
public Memento createMemento(){
return new Memento(state);
}
//恢复状态
public void restoreMemento(Memento memento){
this.state = memento.getState();
}
}
//备忘录角色
class Memento{
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
//管理者,存储备忘录角色
class Caretaker{
private List<Memento> mementoList = new ArrayList<>();
public Memento getMemento(int index) {
//因为列表从0开始,设置index-1
return mementoList.get(index-1);
}
public void addMemento(Memento memento) {
mementoList.add(memento);
}
}
class Client{
public static void main(String[] args) {
//创建发起人,初始状态
Originator originator = new Originator("=== 现在是状态1 ===");
//保存初始状态
Memento memento = originator.createMemento();
//创建管理者,保存备忘录角色
Caretaker caretaker = new Caretaker();
caretaker.addMemento(memento);
//第二个状态
originator.setState("=== 现在是状态2 ===");
//保存第二个状态
Memento memento1 = originator.createMemento();
caretaker.addMemento(memento1);
//第三个状态
originator.setState("=== 现在是状态3 ===");
System.out.println("现在是什么状态?");
System.out.println("现在发起人是:"+originator.getState());
System.out.println("回到第一个状态");
originator.restoreMemento(caretaker.getMemento(1));
System.out.println(originator.getState());
}
}
可以看到,我们可以轻松的回到第一个状态
模式分析
-
需要防止发起人以外的其他对象访问备忘录,避免对备忘录角色的影响
-
备忘录对象通常封装了发起人的部分或所有的状态信息,而且这些状态不能被其他对象访问,也就是说不能在备忘录对象之外保存原发器状态,因为暴露其内部状态将违反封装的原则,可能有损系统的可靠性和可扩展性
-
为了实现对备忘录对象的封装,需要对备忘录的调用进行控制:
发起人,它可以调用备忘录的所有信息,允许发起人访问返回到先前状态所需的所有数据;
管理者,只负责备忘录的保存并将备忘录传递给其他对象;
其他对象,只需要从管理者处取出备忘录对象并将发起人对象的状态恢复,而无须关心备忘录的保存细节 -
理想的情况是只允许生成该备忘录的那个发起人访问备忘录的内部状态
模式优缺点
优点
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原。
- 实现了信息的封装,一个备忘录对象是一种对发起人对象的表示,不会被其他代码改动,这种模式简化了发起人对象,备忘录只保存发起人的状态,可以采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在管理者中定义集合对象来存储多个备忘录
缺点
很明显,如果每一个状态都要保存,就需要创建很多个备忘录对象
缺点就是:资源消耗过大
这也是为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了
适用场景
- 保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态,也就是提供保存和恢复状态的功能
- 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过管理者可以间接访问其内部状态
具体运用
- 撤销功能:ctrl+z
- 数据库回滚
模式扩展
与原型模式联用
既然每一次都是保存同一个对象的状态,可以用原型模式的clone方法
根据要保存的对象的引用,选择clone深克隆、浅克隆方法,就可以取代备忘录角色
发起人中重写clone方法,在客户类时直接clone放置到管理者中即可
封装性
前面说了,理想情况下只允许生成该备忘录的那个发起人访问备忘录的内部状态
那么就需要将备忘录角色仅与发起人关联
在Java中可以通过包私有,也可以将备忘录设置成内部类,使得备忘录角色仅发起人可见
总结
- 备忘录模式提供一个备忘录角色保存对象的内部状态,并使用管理者将备忘录角色存储,以便后续恢复状态
- 备忘录模式有3个角色:发起人(原发器对象)、备忘录角色、管理者
- 发起人通过备忘录角色记录状态,提供创建备忘录,恢复状态的功能;备忘录角色存储发起人的状态,提供状态数据给发起人;管理者管理备忘录角色,提供存储和获得备忘录的功能
- 备忘录模式的理想状态是仅发起人能够访问备忘录的内部状态
- 备忘录模式的优点:提供了状态恢复的实现机制;实现了信息的封装;缺点是资源消耗过大
- 备忘录模式适用场景:需要保存对象状态,提供撤回功能
- 备忘录模式与原型模式联用,通过clone方法直接备份对象数据到管理者,不需要备忘录角色
- 实现封装性,将备忘录角色仅与发起人关联,Java可以通过包私有或者内部类实现