18 备忘录模式
-
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存该状态。这样以后就可将该对象恢复到原先保存的状态
-
理解
- Originator:原发器,使用备忘录来保存某个时刻原发器自身的状态,也可以使用备忘录来恢复内部状态
- Memento:备忘录,存储原发器对象的内部状态,原发器外部的对象不应该能访问到备忘录对象的内部数据
- Caretaker:备忘录管理者,主要负责保存备忘录对象,但是没有权限对备忘录对象的内容进行操作或检查
-
优点
- 更好的封装性:外界无法访问Memento中细节,只有Originator内部可以
- 简化了原发器:备忘录对象被保存到原发器对象之外,让客户来管理他们请求的状态,从而让原发器对象得到简化
- 窄接口和宽接口:备忘录模式,通过引入窄接口和宽接口,使得不同的地方,对备忘录对象的访问是不一样的。窄接口保证了只有原发器才可以访问备忘录对象的状态
-
缺点
- 可能会导致高开销:备忘录模式基本的功能,就是对备忘录对象的存储和恢复,它的基本实现方式就是缓存备忘录对象。这样一来,如果需要缓存的数据量很大,或者是特别频繁的创建备忘录对象,开销是很大的
-
本质:保存和恢复内部状态
-
使用场景
- 如果必须保存一个对象在某一个时刻的全部或者部分状态,这样在以后需要的时候,可以把该对象恢复到先前的状态。可以使用备忘录模式,使用备忘录对象来封装和保存需要保存的内部状态,然后把备忘录对象保存到管理者对象里面,在需要的时候,再从管理者对象里面获取备忘录对象,来恢复对象的状态
- 如果需要保存一个对象的内部状态,但是如果用接口来让其它对象直接得到这些需要保存的状态,将会暴露对象的实现细节并破坏对象的封装性。可以使用备忘录模式,把备忘录对象实现成为原发器对象的内部类,而且还是私有的,从而保证只有原发器对象才能访问该备忘录对象。这样既保存了需要保存的状态,又不会暴露原发器对象的内部实现细节
-
类图
18.1 代码
-
FlowAMockMemento:Memento
/** * 模拟运行流程A的对象的备忘录接口,是个窄接口 */ public interface FlowAMockMemento { //空的 }
-
FlowAMock:Originator
/** * 模拟运行流程A,只是一个示意,代指某个具体流程 */ public class FlowAMock { /** * 流程名称,不需要外部存储的状态数据 */ private String flowName; /** * 示意,代指某个中间结果,需要外部存储的状态数据 */ private int tempResult; /** * 示意,代指某个中间结果,需要外部存储的状态数据 */ private String tempState; /** * 构造方法,传入流程名称 * @param flowName 流程名称 */ public FlowAMock(String flowName){ this.flowName = flowName; } /** * 示意,运行流程的第一个阶段 */ public void runPhaseOne(){ //在这个阶段,可能产生了中间结果,示意一下 tempResult = 3; tempState = "PhaseOne"; } /** * 示意,按照方案一来运行流程后半部分 */ public void schema1(){ //示意,需要使用第一个阶段产生的数据 this.tempState += ",Schema1"; System.out.println(this.tempState + " : now run "+tempResult); this.tempResult += 11; } /** * 示意,按照方案二来运行流程后半部分 */ public void schema2(){ //示意,需要使用第一个阶段产生的数据 this.tempState += ",Schema2"; System.out.println(this.tempState + " : now run "+tempResult); this.tempResult += 22; } /** * 创建保存原发器对象的状态的备忘录对象 * @return 创建好的备忘录对象 */ public FlowAMockMemento createMemento() { return new MementoImpl(this.tempResult,this.tempState); } /** * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态 * @param memento 记录有原发器状态的备忘录对象 */ public void setMemento(FlowAMockMemento memento) { MementoImpl mementoImpl = (MementoImpl)memento; this.tempResult = mementoImpl.getTempResult(); this.tempState = mementoImpl.getTempState(); } /** * 真正的备忘录对象,实现备忘录窄接口 * 实现成私有的内部类,不让外部访问 */ private static class MementoImpl implements FlowAMockMemento{ /** * 示意,保存某个中间结果 */ private int tempResult; /** * 示意,保存某个中间结果 */ private String tempState; public MementoImpl(int tempResult,String tempState){ this.tempResult = tempResult; this.tempState = tempState; } public int getTempResult() { return tempResult; } public String getTempState() { return tempState; } } }
-
FlowAMementoCareTaker:CareTaker
/** * 负责保存模拟运行流程A的对象的备忘录对象 */ public class FlowAMementoCareTaker { /** * 记录被保存的备忘录对象 */ private FlowAMockMemento memento = null; /** * 保存备忘录对象 * @param memento 被保存的备忘录对象 */ public void saveMemento(FlowAMockMemento memento){ this.memento = memento; } /** * 获取被保存的备忘录对象 * @return 被保存的备忘录对象 */ public FlowAMockMemento retriveMemento(){ return this.memento; } }
-
Client
public class Client { public static void main(String[] args) { // 创建模拟运行流程的对象 FlowAMock mock = new FlowAMock("TestFlow"); //运行流程的第一个阶段 mock.runPhaseOne(); //创建一个管理者 FlowAMementoCareTaker careTaker = new FlowAMementoCareTaker(); //创建此时对象的备忘录对象,并保存到管理者对象那里,后面要用 FlowAMockMemento memento = mock.createMemento(); careTaker.saveMemento(memento); //按照方案一来运行流程后半部分 mock.schema1(); //从管理者获取备忘录对象,然后设置回去, //让模拟运行流程的对象自己恢复自己的内部状态 mock.setMemento(careTaker.retriveMemento()); //按照方案二来运行流程后半部分 mock.schema2(); } }
18.2 双重接口问题
- 所谓双重接口,就是对某一个对象提供宽接口,而对另一个对象提供窄接口
- 案例中,对CareTaker提供的是窄接口FlowAMockMemento,无法访问对象的任何内容,对FlowAMock提供的是宽接口MementoImpl,因此其内部可以访问MementoImpl的方法