备忘录模式是使用一个备忘录对象把另外一个对象内部状态进行保存,在适当的时候还原到某个状态。如同我们记录某件事件,在需要回忆的时候去看下记事本。
先来看下类图
该模式涉及到3个角色:
- 发起人角色:
Originator
,该角色包含备忘录对象,备忘录对象存储了他的状态; - 负责人角色:
Caretaker
,该角色保存备忘录对象,但不检查备忘录对象内容; - 备忘录角色:
Memento
,将发起人对象的状态保存起来,,保护发起人的内容不被外界访问
宽接口与白箱
备忘录角色对如何其他对象提供一个接口,也就是宽接口的话,那么备忘录角色存储的内部状态都暴露给其他对象。这种情况导致发起人的状态都没看到,是破坏封装性的,只能通过程序猿的自律。先来看下宽接口。
接下来看下代码实现:
public class Memento {
private String state;
public Memento(String state) {
this.state=state;
}
public String getState() {
return this.state;
}
public void setState(String state){
this.state=state;
}
}
上面这个备忘录对象提供:1、对发起人的状态进行保存;2、可以访问的状态是公开的;
//发起人
public class Originator {
private String state;
//创建备忘录对象来保存状态
public Memento createMemento(){
return new Memento(state);
}
//从备忘录对象里面恢复状态
public void restoreMemento(Memento m){
this.state=m.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
System.out.println("当前状态:"+state);
this.state = state;
}
}
发起人对象利用创建一个新的备忘录对象保存自己的状态;
//负责人
public class Caretaker {
private Memento memento;
public Memento retrieveMemento(){
return this.memento;
}
public void saveMemento(Memento m){
this.memento=m;
}
}
负责人只负责保存备忘录对象,对备忘录对象里面的内容不再变化。客户端的操作如下
public class Client {
public static void main(String[] args) {
Originator o=new Originator();
Caretaker c=new Caretaker();
//发起人状态改变
o.setState("Start");
//负责人保存这个备忘录
c.saveMemento(o.createMemento());
//改变状态
o.setState("End");
//回到初始
o.restoreMemento(c.retrieveMemento());
}
}
/** ---- result ----
当前状态:Start
当前状态:End
*/
上面就是白箱操作,很简单,但是破坏了对发起人的状态封装;
双重接口
所谓双重接口就是对某一个对象提供宽接口,对另外一些类提供窄接口。系统中可能需要将某个对象的状态保存起来,在某个时候进行恢复,但这些状态并不希望被外界访问,以免有外界直接修改状态的危险,这个时候,备忘录模式就很好的解决这个问题,他利用宽接口和窄接口来保证。
假设窄接口对所有类公开,而公开类只对某一个公开,这个时候,我们可以把实现了宽接口和窄接口的具体类作为这个特殊类的内部类,宽接口的方法也可以移植到这个特殊类上,而具体类里面的方法都是私有,这样对特殊类可以访问所有接口,其他类智能调用特殊类的某些公开方法。有点饶人,画个图
图1是一个基本样子,进行演变,首先宽接口方法归属到具体类里面,变成下面这个样子
这样宽接口的方法还是公开的,此时把具体类作为特殊类的内部类,并且,把里面的方法都设置私有
这个时候的特殊类,也就发起者代码如下:
public class DoubleInterfaceDemo {
public static void main(String[] args) {
DoubleInterfaceDemo demo=new DoubleInterfaceDemo();
demo.new ConcreteCLass().wide();
demo.new ConcreteCLass().getConcrete().does();
}
class ConcreteCLass implements Narrow{
@Override
public void does() {
System.out.println("窄接口");
}
//这个接口只能DoubleInterfaceDemo自己用了
private void wide(){
System.out.println("宽接口");
}
public Narrow getConcrete(){
return (Narrow)new ConcreteCLass();
}
}
}
接下来看下黑箱的备忘录模式,有了上面的介绍,那我们把备忘录的具体实现作为内部类放到发起人对象里面
下面看下具体代码
//发起人 加强版 黑箱
public class Originator2 {
private String state;
public Originator2() {
}
public MementoIF createMemento(){
return new Memento2(this.state);
}
//内部类 备忘录
protected class Memento2 implements MementoIF{
private String saveState;
public Memento2(String saveState) {
this.saveState=saveState;
}
public String getSaveState() {
return saveState;
}
public void setSaveState(String saveState) {
this.saveState = saveState;
}
}
//从备忘录对象里面恢复状态
public void restoreMemento(MementoIF m){
this.state=((Memento2)m).getSaveState();
}
public String getState() {
return state;
}
public void setState(String state) {
System.out.println("当前状态:"+state);
this.state = state;
}
}
负责人针对接口编程,代码如下
//负责人
public class Caretaker2 {
private MementoIF memento;
public MementoIF retrieveMemento(){
return this.memento;
}
public void saveMemento(MementoIF m){
this.memento=m;
}
}
MementoIF
这个接口是窄接口,里面可以有公共使用的方法,这里假设没有方法。
最后测试下这个代码
public class Client2 {
public static void main(String[] args) {
Originator2 o=new Originator2();
Caretaker2 c=new Caretaker2();
//发起人状态改变
o.setState("Start");
//负责人保存这个备忘录
c.saveMemento(o.createMemento());
//改变状态
o.setState("End");
//回到初始
o.restoreMemento(c.retrieveMemento());
}
}
/**
最后结论和之前的一样,但在设计上面已经很不同
*/
有时候发起人内部信息需要保存在别的地方,但是读取还是发起人自己,此时备忘录模式就可以把发起人信息对外封闭起来。