备忘录(Memento)模式

  备忘录模式又叫作快照模式或者Token模式。java

  备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而能够在未来合适的时候把这个对象还原到存储起来的状态。备忘录模式经常与命令模式和迭代子模式一块儿使用。数据库

  常见的系统每每不止存储一个状态,而是须要存储多个状态。这些状态经常是一个对象历史发展的不一样阶段的快照,存储这些快照的备忘录对象叫作此对象的历史;某一个快照所处的位置叫作检查点。ide

1.角色

 1.备忘录角色

备忘录角色有以下责任:函数

  (1)将发起人(Originator)对象的内战状态存储起来。备忘录能够根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。this

  (2)备忘录能够保护其内容不被发起人(Originator)对象以外的任何对象所读取。spa

  备忘录有两个等效的接口:设计

  ●  窄接口:负责人(Caretaker)对象(和其余除发起人对象以外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只容许它把备忘录对象传给其余的对象。rest

  ●  宽接口:与负责人对象看到的窄接口相反的是,发起人对象能够看到一个宽接口(wide interface),这个宽接口容许它读取全部的数据,以便根据这些数据恢复这个发起人对象的内部状态。code

2.  发起人角色

发起人角色有以下责任:对象

  (1)建立一个含有当前的内部状态的备忘录对象。

  (2)使用备忘录对象存储其内部状态。

3.负责人(Caretaker)角色

负责人角色有以下责任:

  (1)负责保存备忘录对象。

  (2)不检查备忘录对象的内容。

 

2.  模式的实现

1. 白箱备忘录模式的实现

  备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对全部对象公开。所以这个实现又叫作“白箱实现”。

结构:

 

 代码以下:

发起人角色代码:利用新建的备忘录对象将本身的内部状态存储起来

/**
 * 发起人角色
 *
 */
public class Originator {

    private String state;

    /**
     * 工厂方法,返回一个新的备忘录对象
     */
    public Memento createMemento() {
        return new Memento(state);
    }

    /**
     * 将发起人恢复到备忘录对象所记载的状态
     */
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        System.out.println("当前状态:" + this.state);
    }
}

 

备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。

/**
 * 备忘录角色
 *
 */
public class Memento {

    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

 

负责人角色类,负责人角色负责保存备忘录对象,可是从不修改(甚至不查看)备忘录对象的内容。

/**
 * 负责人角色
 *
 */
public class Caretaker {

    private Memento memento;

    /**
     * 备忘录的取值方法
     */
    public Memento retrieveMemento() {
        return this.memento;
    }

    /**
     * 备忘录的赋值方法
     */
    public void saveMemento(Memento memento) {
        this.memento = memento;
    }
}

 

客户端:

public class Client {

    public static void main(String[] args) {
        // 发起人,改变负责人对象的状态
        Originator o = new Originator();
        o.setState("On");

        // 利用发起人建立备忘录
        Memento memento = o.createMemento();

        // 负责人,并将发起人对象的备忘录储存起来
        Caretaker c = new Caretaker();
        c.saveMemento(memento);

        // 修改发起人的状态
        o.setState("Off");

        // 获取负责人保存的备忘录状态
        Memento retrieveMemento = c.retrieveMemento();

        // 恢复发起人对象的状态
        o.restoreMemento(retrieveMemento);
        System.out.println(o.getState());
    }
}

结果:

当前状态:On
当前状态:Off
On

    在上面客户端角色里面,首先将发起人对象的状态设置成“On”,并建立一个备忘录对象将这个状态存储起来;而后将发起人对象的状态改为“Off”;最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。

 

时序图以下:

将发起人对象的状态存储到白箱备忘录对象中去的时序图:

能够看出系统运行的时序是这样的:
(1)将发起人对象的状态设置成“On”。
(2)调用发起人角色的createMemento()方法,建立一个备忘录对象将这个状态存储起来。
(3)将备忘录对象存储到负责人对象中去。

 

将发起人对象恢复到备忘录对象所记录的状态的时序图以下所示:

能够看出,将发起人对象恢复到备忘录对象所记录的状态时,系统的运行时序是这样的:
(1)将发起人状态设置成“Off”。
(2)将备忘录对象从负责人对象中取出。
(3)将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。

 

白箱实现的优缺点:

  优势是实现比较简单,经常用做教学目的。缺点是破坏对发起人状态的封装。(并且上面的例子只能存储一个状态,又叫作一个检查点)

 

2.黑箱实现

  备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其余对象提供一个窄接口。这样的实现叫作“黑箱实现”。

  在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。
  将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其余对象。这样,Originator类看到的是Menmento的全部接口,而Caretaker以及其余对象看到的仅仅是标识接口MementoIF所暴露出来的接口。

类图以下:

 

源码:

发起人角色:定义了一个内部的Memento类。因为此Memento类的所有接口都是私有的,所以只有它本身和发起人类能够调用。

/**
 * 发起人角色
 */
public class Originator {

    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        System.out.println("赋值状态:" + state);
    }

    /**
     * 工厂方法,返还一个新的备忘录对象
     */
    public MementoIF createMemento() {
        return new Memento(state);
    }

    /**
     * 发起人恢复到备忘录对象记录的状态
     */
    public void restoreMemento(MementoIF memento) {
        this.setState(((Memento) memento).getState());
    }

    /**
     * 私有内部备忘录角色实现宽接口
     * 
     * @author Administrator
     *
     */
    private class Memento implements MementoIF {

        private String state;

        /**
         * 构造方法
         */
        private Memento(String state) {
            this.state = state;
        }

        private String getState() {
            return state;
        }

        private void setState(String state) {
            this.state = state;
        }
    }
}

 

窄接口MementoIF,这是一个标识接口,所以它没有定义出任何的方法。

/**
 * 标识接口
 *
 */
public interface MementoIF {

}

 

负责人角色类Caretaker可以获得的备忘录对象是以MementoIF为接口的,因为这个接口仅仅是一个标识接口,所以负责人角色不可能改变这个备忘录对象的内容。

/**
 * 负责人角色
 * 
 * @author Administrator
 *
 */
public class Caretaker {

    private MementoIF memento;

    /**
     * 备忘录取值方法
     */
    public MementoIF retrieveMemento() {
        return memento;
    }

    /**
     * 备忘录赋值方法
     */
    public void saveMemento(MementoIF memento) {
        this.memento = memento;
    }
}

 

客户端:

public class Client {

    public static void main(String[] args) {
        // 建立发起人
        Originator o = new Originator();

        // 建立负责人
        Caretaker c = new Caretaker();

        // 改变负责人对象的状态
        o.setState("On");

        // 建立备忘录对象,并将发起人对象的状态存储起来
        c.saveMemento(o.createMemento());

        // 修改发起人对象的状态
        o.setState("Off");

        // 恢复发起人对象的状态
        o.restoreMemento(c.retrieveMemento());
    }
}

结果:

赋值状态:On
赋值状态:Off
赋值状态:On

 

时序以下:

(1)将发起人对象的状态设置为“On”。
(2)调用createMemento()方法,建立一个备忘录对象将这个状态存储起来(此时createMemento()方法还回的明显类型是MementoIF接口,真实类型为Originator内部的Memento对象)。
(3)将备忘录对象存储到负责人对象中去。因为负责人对象拿到的仅是MementoIF接口,所以没法读出备忘录对象内部的状态。
(4)将发起人对象的状态设置为“Off”。
(5)调用负责人对象的retrieveMemento()方法将备忘录对象取出。注意此时仅能获得MementoIF接口,所以没法读出此对象的内部状态。
(6)调用发起人对象的restoreMemento()方法将发起人对象的状态恢复成备忘录对象所存储的起来的状态,即“On”状态。因为发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,所以发起人对象能够利用内部类Memento的私有接口读出此对象的内部状态。

 

3.多重检查点

  前面的例子都是只存储一个状态的简单实现,也能够叫作只有一个检查点。常见的系统每每须要存储不止一个状态,而是须要存储多个状态,或者叫作有多个检查点。

  备忘录模式能够将发起人对象的状态存储到备忘录对象里面,备忘录模式能够将发起人对象恢复到备忘录对象所存储的某一个检查点上。下面给出一个示意性的、有多重检查点的备忘录模式的实现。

类图以下:

 

源代码:

发起人角色:将状态存在集合中,每个状态都有一个指数index,叫作检查点指数。

import java.util.ArrayList;
import java.util.List;

/**
 * 发起人角色
 * 
 * @author Administrator
 *
 */
public class Originator {

    private List<String> states;
    // 检查点指数
    private int index;

    /**
     * 构造函数
     */
    public Originator() {
        states = new ArrayList<String>();
        index = 0;
    }

    /**
     * 工厂方法,返还一个新的备忘录对象
     */
    public Memento createMemento() {
        return new Memento(states, index);
    }

    /**
     * 将发起人恢复到备忘录对象记录的状态上
     */
    public void restoreMemento(Memento memento) {
        states = memento.getStates();
        index = memento.getIndex();
    }

    /**
     * 状态的赋值方法
     */
    public void setState(String state) {
        states.add(state);
        index++;
    }

    /**
     * 辅助方法,打印全部状态
     */
    public void printStates() {
        for (String state : states) {
            System.out.println(state);
        }
    }
}

 

备忘录角色:存储任意多的状态,外界能够用检查点指数index来取出检查点上的状态(克隆了传入的states,不可使用同一个引用)

import java.util.ArrayList;
import java.util.List;

/**
 * 备忘录角色
 * 
 * @author Administrator
 *
 */
public class Memento {

    private List<String> states;
    private int index;

    /**
     * 构造函数
     */
    public Memento(List<String> states, int index) {
        this.states = new ArrayList<String>(states);
        this.index = index;
    }

    public List<String> getStates() {
        return states;
    }

    public int getIndex() {
        return index;
    }

}

 

负责人角色:根据检查点指数index来恢复发起人角色的状态,也能够根据检查点指数index来取消一个检查点。

import java.util.ArrayList;
import java.util.List;

/**
 * 负责人角色
 * 
 * @author Administrator
 *
 */
public class Caretaker {

    private Originator o;
    private List<Memento> mementos = new ArrayList<Memento>();
    private int current;

    /**
     * 构造函数
     */
    public Caretaker(Originator o) {
        this.o = o;
        current = 0;
    }

    /**
     * 建立一个新的检查点
     */
    public int createMemento() {
        Memento memento = o.createMemento();
        mementos.add(memento);
        return current++;
    }

    /**
     * 将发起人恢复到某个检查点
     */
    public void restoreMemento(int index) {
        Memento memento = mementos.get(index);
        o.restoreMemento(memento);
    }

    /**
     * 将某个检查点删除
     */
    public void removeMemento(int index) {
        mementos.remove(index);
    }
}

 

客户端:

public class Client {
    public static void main(String[] args) {
        Originator o = new Originator();
        Caretaker c = new Caretaker(o);

        // 改变状态
        o.setState("state 0");
        // 创建一个检查点
        c.createMemento();

        // 改变状态
        o.setState("state 1");
        // 创建一个检查点
        c.createMemento();

        // 改变状态
        o.setState("state 2");
        // 创建一个检查点
        c.createMemento();

        // 改变状态
        o.setState("state 3");
        // 创建一个检查点
        c.createMemento();

        // 打印出全部检查点
        o.printStates();

        System.out.println("-----------------恢复检查点2-----------------");
        // 恢复到第二个检查点
        c.restoreMemento(1);
        // 打印出全部检查点
        o.printStates();
    }
}

结果:

state 0
state 1
state 2
state 3
-----------------恢复检查点2-----------------
state 0
state 1
state 2

 

  能够看出,客户端角色经过不断改变发起人角色的状态,并将之存储在备忘录里面。经过指明检查点指数能够将发起人角色恢复到相应的检查点所对应的状态上。

 

4.自述历史模式

  所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。虽然在实现上备忘录类能够成为发起人类的内部成员类,可是备忘录类仍然保持做为一个角色的独立意义。在“自述历史”模式里面,发起人角色本身兼任负责人角色。

  备忘录角色有以下责任:
  (1)将发起人(Originator)对象的内部状态存储起来。
  (2)备忘录能够保护其内容不被发起人(Originator)对象以外的任何对象所读取。
  发起人角色有以下责任:
  (1)建立一个含有它当前的内部状态的备忘录对象。
  (2)使用备忘录对象存储其内部状态。
  客户端角色有负责保存备忘录对象的责任。

 

源代码:

/**
 * 标识接口(做为备忘录对象的身份和标识)
 * 
 * @author Administrator
 *
 */
public interface MementoIF {

}

 

发起人角色:

/**
 * 发起人角色(兼职发起人角色)
 * 
 * @author Administrator
 *
 */
public class Originator {

    public String state;

    /**
     * 改变状态
     */
    public void changeState(String state) {
        this.state = state;
        System.out.println("状态改变为:" + state);
    }

    /**
     * 工厂方法,返还一个新的备忘录对象
     */
    public Memento createMemento() {
        return new Memento(this);
    }

    /**
     * 将发起人恢复到备忘录对象所记录的状态上
     */
    public void restoreMemento(MementoIF memento) {
        Memento m = (Memento) memento;
        changeState(m.state);
    }

    private class Memento implements MementoIF {

        private String state;

        /**
         * 构造方法
         */
        private Memento(Originator o) {
            this.state = o.state;
        }

        private String getState() {
            return state;
        }

    }
}

 

客户端:

public class Client {

    public static void main(String[] args) {
        Originator o = new Originator();
        // 修改状态
        o.changeState("state 0");
        // 建立备忘录
        MementoIF memento = o.createMemento();

        // 修改状态
        o.changeState("state 1");

        // 按照备忘录恢复对象的状态
        o.restoreMemento(memento);
    }

}

结果:

状态改变为:state 0
状态改变为:state 1
状态改变为:state 0

 

  此种模式简单易懂,多是备忘录模式最为流行的实现形式。

 

总结:

意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象以外保存这个状态。

主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象以外保存这个状态,这样能够在之后将对象恢复到原先保存的状态。

什么时候使用:不少时候咱们老是须要记录一个对象的内部状态,这样作的目的就是为了容许用户取消不肯定或者错误的操做,可以恢复到他原先的状态,使得他有"后悔药"可吃。

如何解决:经过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例: 一、后悔药。 二、打游戏时的存档。 三、Windows 里的 ctri + z。 四、IE 中的后退。 四、数据库的事务管理。

优势: 一、给用户提供了一种能够恢复状态的机制,可使用户可以比较方便地回到某个历史的状态。 二、实现了信息的封装,使得用户不须要关心状态的保存细节。

缺点:消耗资源。若是类的成员变量过多,势必会占用比较大的资源,并且每一次保存都会消耗必定的内存。

使用场景: 一、须要保存/恢复数据的相关状态场景。 二、提供一个可回滚的操做。

注意事项: 一、为了符合迪米特原则,还要增长一个管理备忘录的类。 二、为了节约内存,可以使用原型模式+备忘录模式。

相关文章
相关标签/搜索