设计模式《备忘录模式》

引言

  上一节我们说了享元模式,这节本来打算说访问者模式的,但是太抽象,而且用的不多,所以本章节讲备忘录模式

示例地址

  Demo

类图

image

定义

  在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将该对象恢复到原先保存的状态。

使用场景

  1. 需要保存一个对象在某一个时刻的状态或部分状态。
  2. 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。

备忘录模式角色

  Originator:负责创建一个备忘录,可以记录、恢复自身的内部状态。同时Originator还可以根据需要决定Memento存储自身的那些内部状态。
  Memento:备忘录角色,用于存储Originator的内部状态,并且可以防止Originator以外的对象访问Memento。
  Caretaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。

备忘录模式中的相关概念

  1. 窄接口:

    只允许它把备忘录对象传给其他的对象。针对的是负责人对象和其他除发起人对象之外的任何对象。

  2. 宽接口:

    允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。针对发起人。

备忘录模式中的四种模式

  • 白箱模式

  将发起人角色的状态存储在一个大家都看得到的地方,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。

1. 定义备忘录
/**
 * 定义备忘录
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午11:23.
 */
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;
    }
}
2. 创建发起人角色
/**
 * 创建发起人角色
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午10:59.
 */
public class Originator {
    private String state;

    public String getState() {
        return state;
    }

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

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

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

    @Override
    public String toString() {
        return "Originator{" +
                "state='" + state + '\'' +
                '}';
    }
}
3. 定义负责人角色
/**
 * 定义负责人角色
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午11:28.
 */
public class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }

}
4. Client
  Originator originator = new Originator();
  originator.setState("state1");
  System.out.println(originator);

  Caretaker caretaker = new Caretaker();
  caretaker.setMemento(originator.createMemento());
  originator.setState("state2");
  System.out.println(originator);

  originator.restoreMemento(caretaker.getMemento());
  System.out.println(originator);
  • 黑箱模式

  备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。 简单说就是备忘录是一个接口,实现类放到发起人里面去了,外界看不见。

1. 定义接口
/**
 * 定义备忘录接口
 *
 * @author 512573717@qq.com
 * @created 2018/8/10  上午10:18.
 */
public interface MementoIF {
}

2. 创建发起人角色
/**
 *  创建发起人角色
 * 
 * @author 512573717@qq.com
 
 * @created 2018/8/10  下午2:04.
 * 
 */
public class Originator {

    public String state;

    public String getState() {
        return state;
    }

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

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

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

    @Override
    public String toString() {
        return "Originator{" +
                "state='" + state + '\'' +
                '}';
    }

    private class Memento implements MementoIF {

        private String state;

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

        private String getState() {
            return state;
        }

    }
}
3. 定义负责人角色
/**
 * 定义负责人角色
 *
 * @author 512573717@qq.com
 * @created 2018/8/10  下午2:04.
 */
public class Caretaker {

    private MementoIF memento;

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

    /**
     * 备忘录赋值方法
     */
    public void saveMemento(MementoIF memento) {
        this.memento = memento;
    }
}
4. Client
    Originator o = new Originator();
    Caretaker c = new Caretaker();
    //改变负责人对象的状态
    o.setState("state1");
    System.out.println("保存中"+o);
    //创建备忘录对象,并将发起人对象的状态存储起来
    c.saveMemento(o.createMemento());
    //修改发起人对象的状态
    o.setState("state2");
    System.out.println("设置新的值"+o);
    //恢复发起人对象的状态
    o.restoreMemento(c.retrieveMemento());
    System.out.println("恢复上次的值"+o);

  • 多重检查点

  常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。用list或者map存储,这种叫做多重检查点

1. 定义备忘录
/**
 * 定义备忘录
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午11:23.
 */
public class Memento {
    private List<String> states;

    /**
     * 构造函数
     */
    public Memento(List<String> states) {
        //每次存放新的
        this.states = new ArrayList<String>(states);
    }

    public List<String> getStates() {
        return states;
    }
}
2. 创建发起人角色
/**
 *  创建发起人角色
 *
 * @author 512573717@qq.com

 * @created 2018/8/10  下午2:04.
 *
 */
public class Originator {
    private List<String> states;
    /**
     * 构造函数
     */
    public Originator(){
        states = new ArrayList<String>();
    }
    /**
     * 工厂方法,返还一个新的备忘录对象
     */
    public Memento createMemento(){
        return new Memento(states );
    }
    /**
     * 将发起人恢复到备忘录对象记录的状态上
     */
    public void restoreMemento(Memento memento){
        states = memento.getStates();
    }
    /**
     * 状态的赋值方法
     */
    public void setState(String state){
        states.add(state);
    }
    /**
     * 辅助方法,打印所有状态
     */
    public void printStates(){

        for(String state : states){
            System.out.println(state);
        }
    }
}
3. 定义负责人角色
/**
 * 定义负责人角色
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午11:28.
 */
public class Caretaker {

    private Originator o;
    private List<Memento> mementos = new ArrayList<Memento>();
    /**
     * 构造函数
     */
    public Caretaker(Originator o){
        this.o = o;
    }
    /**
     * 创建一个新的检查点
     */
    public void createMemento(){
        Memento memento = o.createMemento();
        mementos.add(memento);
    }
    /**
     * 将发起人恢复到某个检查点
     */
    public void restoreMemento(int index){
        Memento memento = mementos.get(index);
        o.restoreMemento(memento);
    }
    /**
     * 将某个检查点删除
     */
    public void removeMemento(int index){
        mementos.remove(index);
    }
}
4. Client
    //多重点检查
    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.setState("state 4");
    //建立一个检查点
    c.createMemento();
    //打印出所有检查点
    o.printStates();
    System.out.println("-----------------恢复检查点-----------------");
    //恢复到第二个检查点
    c.restoreMemento(2);
    //打印出所有检查点
    o.printStates();
  • 自述历史模式

  发起人角色自己兼任负责人角色。

1. 定义备忘录接口
/**
 * 定义备忘录接口
 *
 * @author 512573717@qq.com
 * @created 2018/8/10  上午10:18.
 */
public interface MementoIF {
}
2. 创建发起人角色
/**
 * 创建发起人角色
 *
 * @author 512573717@qq.com
 * @created 2018/8/10  下午2:04.
 */
public class Originator {

    public String state;


    /**
     * 改变状态
     */
    public void setState(String state) {
        this.state = state;
    }

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

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

    @Override
    public String toString() {
        return "Originator{" +
                "state='" + state + '\'' +
                '}';
    }

    private class Memento implements MementoIF {

        private String state;

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

        private String getState() {
            return state;
        }

    }
}
3. Client
    Originator o = new Originator();
    //修改状态
    o.setState("state 0");
    //创建备忘录
    MementoIF memento = o.createMemento();
    //修改状态
    o.setState("state 1");
    //按照备忘录恢复对象的状态
    o.restoreMemento(memento);

备忘录模式示例

  最近腾讯有一款很火的游戏,叫做王者荣耀,当我们正在打游戏的时候,突然来电话了,这个时候怎么处理。来看看我们的备忘录模式怎么实现。

1. 发起人角色(王者荣耀)
/**
 * 王者荣耀 游戏
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  上午11:53.
 */
public class WangZheRongYao {

    //开始的总时长
    private int time;

    // 当前的等级
    private int level;

    // 是否退出
    private boolean isExit = false;

    /**
     * 开始一把排位
     */
    public void playGame() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!isExit) {
                    System.out.println("游戏开始了:" + time + "分钟,等级:" + level);
                    time++;
                    level++;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    /**
     * 来电话了,退出当前游戏
     */
    public void exitGame() {
        isExit = true;
        System.out.println("=====来电话了,退出当前游戏=====");
        System.out.println("游戏开始了:" + time + "分钟,等级:" + level);
    }

    /**
     * 保存获取当前游戏信息
     *
     * @return
     */
    public GameInfo saveGameInfo() {
        return new GameInfo(time, level);
    }

    /**
     * 重新加载游戏
     *
     * @param gameInfo
     */
    public void resetGame(GameInfo gameInfo) {
        time = gameInfo.getTime();
        level = gameInfo.getLevel();
        System.out.println("=====恢复游戏=====");
        System.out.println("游戏开始了:" + time + "分钟,等级:" + level);
//        isExit = false;
    }
}
2. 备忘录
/**
 * 创建备忘录
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  下午1:57.
 */
public class GameInfo {
    //开始的总时长
    private int time;
    // 当前的等级
    private int level;

    public GameInfo(int time, int level) {
        this.time = time;
        this.level = level;
    }

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }
}
3. 负责人角色
/**
 * 备忘录管理者
 *
 * @author 512573717@qq.com
 * @created 2018/8/9  下午1:59.
 */
public class GameManager {
    private GameInfo mGameInfo;

    private static volatile GameManager instance;

    private GameManager() {
    }

    public static GameManager getGameManager() {
        if (instance == null) {
            synchronized (GameManager.class) {
                if (instance == null) {
                    instance = new GameManager();
                }
            }
        }
        return instance;
    }

    /**
     * 保存游戏信息
     *
     * @param gameInfo
     */
    public void saveGameInfo(GameInfo gameInfo) {
        mGameInfo = gameInfo;
    }

    /**
     * 读取游戏信息
     *
     * @return
     */
    public GameInfo getGameInfo() {
        return mGameInfo;
    }
}
4. Client
    WangZheRongYao wzry = new WangZheRongYao();
    wzry.playGame();
    try {
        //玩了一会
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //暂停游戏
    wzry.exitGame();
    GameManager.getGameManager().saveGameInfo(wzry.saveGameInfo());

    //恢复游戏
    wzry.resetGame(GameManager.getGameManager().getGameInfo());

总结

优点

   1. 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
  2. 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点

  资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容