前言
网上搜索备忘录设计模式,基本上均是在一个GoF,基础上衍生下来的。为了避免重复造轮子,这里会结合网上demo,和自己理解进行总结
定义:备忘录(Memento)模式又称标记(Token)模式。GOF给备忘录模式的定义为:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
角色分类:
1) 备忘录(Memento)角色:备忘录角色存储“备忘发起角色”的内部状态。“备忘发起角色”根据需要决定备忘录角色存储“备忘发起角色”的哪些内部状态。为了防止“备忘发起角色”以外的其他对象访问备忘录。备忘录实际上有两个接口,“备忘录管理者角色”只能看到备忘录提供的窄接口——对于备忘录角色中存放的属性是不可见的。“备忘发起角色”则能够看到一个宽接口——能够得到自己放入备忘录角色中属性。
2) 备忘发起(Originator)角色:“备忘发起角色”创建一个备忘录,用以记录当前时刻它的内部状态。在需要时使用备忘录恢复内部状态。
3) 备忘录管理者(Caretaker)角色:负责保存好备忘录。不能对备忘录的内容进行操作或检查。
角色职责:
发起人角色
发起人角色有如下责任:
(1)创建一个含有当前的内部状态的备忘录对象。
(2)使用备忘录对象存储其内部状态。
负责人角色
负责人角色有如下责任:
(1)负责保存备忘录对象。
(2)不检查备忘录对象的内容。
分类:
1.”白箱”备忘录模式的实现
2.“黑箱”备忘录模式的实现
3.“多重”检查点
4.”自述历史”模式
三、举例
按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。而对于其他角色则是不可见的。GOF在书中以C++为例进行了探讨。但是在Java中没有提供类似于C++中友元的概念。在Java中怎样才能保持备忘录角色的封装呢?
下面对三种在Java中可保存封装的方法进行探讨。
第一种就是采用两个不同的接口类来限制访问权限。这两个接口类中,一个提供比较完备的操作状态的方法,我们称它为宽接口;而另一个则可以只是一个标示,我们称它为窄接口。备忘录角色要实现这两个接口类。这样对于“备忘发起角色”采用宽接口进行访问,而对于其他的角色或者对象则采用窄接口进行访问。
这种实现比较简单,但是需要人为的进行规范约束——而这往往是没有力度的。
第二种方法便很好的解决了第一种的缺陷:采用内部类来控制访问权限。将备忘录角色作为“备忘发起角色”的一个私有内部类。好处我不详细解释了,看看代码吧就明白了。下面的代码是一个完整的备忘录模式的教学程序。它便采用了第二种方法来实现备忘录模式。
还有一点值得指出的是,在下面的代码中,对于客户程序来说“备忘录管理者角色”是不可见的,这样简化了客户程序使用备忘录模式的难度。下面采用“备忘发起角色”来调用访问“备忘录管理者角色”,也可以参考门面模式在客户程序与备忘录角色之间添加一个门面角色。
第三种方式是不太推荐使用的:使用clone方法来简化备忘录模式。由于Java提供了clone机制,这使得复制一个对象变得轻松起来。使用了clone机制的备忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是在为你的类实现clone方法时要慎重啊。
在上面的教学代码中,我们简单的模拟了备忘录模式的整个流程。在实际应用中,我们往往需要保存大量“备忘发起角色”的历史状态。这时就要对我们的“备忘录管理者角色”进行改造,最简单的方式就是采用容器来按照顺序存放备忘录角色。这样就可以很好的实现undo、redo功能了。
“白箱”备忘录模式的实现
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。
“白箱”实现将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。
下面给出一个示意性的“白箱实现”。
demo 入口
package com.wc.momoto;
/**
* 类似白箱备忘录模式
* @author weichyang
*
*/
public class Client {
/**
* 客户端
*
* @param args
*/
public static void main(String[] args) {
int state = 3;
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState(state);
/**
* 创建备忘录对象的 缓存起来
*/
caretaker.saveMemento(originator.creatMementoObject());
/*
* 进行设置重新还原
*/
originator.setState(5);
System.out.println("发起人更改状态:" + originator.getState());
originator.restoreMemento(caretaker.retrieveMemento());
}
}
管理者,负责发起者的管理
package com.wc.momoto;
/**
* 管理者 负责管理Caretaker
*
* @author weichyang
*
*/
public class Caretaker {
private Memento memento;
/**
* 备忘录的取值方法
*/
public Memento retrieveMemento() {
return this.memento;
}
/**
* 备忘录的赋值方法
*/
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
备忘录,对发起者进行缓存的类
package com.wc.momoto;
/**
* 备忘录
*
* @author weichyang
*
*/
public class Memento {
private int state;
public Memento() {
super();
}
public Memento(int state) {
this.state = state;
System.out.println("备忘录 当前保存 状态:" + state);
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
发起者,负责备忘录的创建,修改,恢复
package com.wc.momoto;
/**
* 发起者
*
* @author weichyang
*
*/
public class Originator {
private int state = 0;
Caretaker caretaker = new Caretaker();
public Memento creatMementoObject() {
return new Memento(state);
}
/**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
System.out.println("恢复 备忘录 状态:" + state);
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
“黑箱”备忘录模式的实现
备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。
在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。
将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口 MementoIF给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他 对象看到的仅仅是标识接口MementoIF所暴露出来的接口。
package com.wc.momoto.black;
/**
* 类似黑箱备忘录模式
*
* @author weichyang
*
*/
public class Client {
/**
* 客户端
*
* @param args
*/
public static void main(String[] args) {
int state = 3;
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState(state);
/**
* 创建备忘录对象的 缓存起来
*/
caretaker.saveMemento(originator.creatMementoObject());
/*
* 进行设置重新还原
*/
originator.setState(5);
System.out.println(" 黑箱发起人更改状态:" + originator.getState());
originator.restoreMemento(caretaker.retrieveMemento());
}
}
package com.wc.momoto.black;
public interface MemotoIF {
}
- 1
- 2
- 3
- 4
- 5
- 6
package com.wc.momoto.black;
/**
* 发起者
* 内部类如何拿到外部类的引用 https://zhidao.baidu.com/question/513464853.html
* 已经外部类如何访问内部类中的成员
* @author weichyang
*
*/
public class Originator {
private int state = 0;
Caretaker caretaker = new Caretaker();
public Memento creatMementoObject() {
return new Memento(state);
}
/**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(MemotoIF momIf) {
this.setState(((Memento) momIf).getState());
System.out.println("黑箱恢复 备忘录 状态:" + state);
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
private class Memento implements MemotoIF {
private int state;
public Memento(int state) {
this.state = state;
System.out.println("黑箱备忘录 当前保存 状态:" + state);
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
package com.wc.momoto.black;
/**
* 管理者 负责管理Caretaker
*
* @author weichyang
*
*/
public class Caretaker {
private MemotoIF memento;
/**
* 备忘录的取值方法
*/
public MemotoIF retrieveMemento() {
return this.memento;
}
/**
* 备忘录的赋值方法
*/
public void saveMemento(MemotoIF memento) {
this.memento = memento;
}
}
多重检查点
前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。
备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。
4.”自述历史”模式
所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录 (Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在“自述历 史”模式里面,发起人角色自己兼任负责人角色。