资讯专栏INFORMATION COLUMN

一起学设计模式 - 备忘录模式

roland_reed / 521人阅读

摘要:备忘录模式常常与命令模式和迭代子模式一同使用。自述历史所谓自述历史模式实际上就是备忘录模式的一个变种。在备忘录模式中,发起人角色负责人角色和备忘录角色都是独立的角色。

备忘录模式(Memento Pattern)属于行为型模式的一种,在不破坏封装特性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。

概述

备忘录模式又叫做快照模式(Snapshot Pattern),一个用来存储另外一个对象内部状态的快照的对象。

备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。

案例

前言:备忘录模式按照备忘录角色的形态不同,分为白箱实现与黑箱实现,两种模式与备忘录角色提供的接口模式有关;

引入两个定义,备忘录有两个等效的接口:

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

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

白箱实现

角色组成

Memento(备忘录角色): 负责存储原发器对象的内部状态,但是具体需要存储哪些数据是由原发器对象来决定的,在需要的时候提供原发器需要的内部状态。PS:这里可以存储状态。

Originator(发起人(原发器)角色): 记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。

Caretaker(备忘录负责人(管理者)角色): 对备忘录对象进行管理,但是不能对备忘录对象的内容进行操作或检查。

备忘录角色对任何对象都提供公共的访问,内部所存储的状态对对象公开,即为白箱实现。白箱实现发起人和负责人提供相同接口,使得负责人可以访问备忘录全部内容,并不安全。白箱实现对备忘录内容的保护靠的是程序员的自律,实现也很简单。

UML结构图

1.创建一个备忘录角色备忘录,内部定义了一个变量用来区分当前对象状态

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;
    }
}

2.接着创建一个原发器对象Originator,定义了创建备忘录对象和回滚的对象

public class Originator {

    private String state;

    public String getState() {
        return this.state;
    }

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

    /**
     * 创建对象
     *
     * @return 备忘录对象
     */
    public Memento createMemento() {
        return new Memento(state);
    }

    /**
     * 从备忘录中恢复
     *
     * @param memento 恢复的对象
     */
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
    }
}

3.创建备忘录管理者Caretaker,顾名思义就是来管理备忘录对象的

public class Caretaker {
    /**
     * 备忘录对象
     */
    private Memento memento;

    /**
     * 获取备忘录
     *
     * @return 备忘录对象
     */
    public Memento retrieveMemento() {
        return this.memento;
    }

    /**
     * 存储备忘录对象
     */
    public void saveMemento(Memento memento) {
        this.memento = memento;
    }
}

4.创建测试类

public class Client {

    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        originator.setState("状态A");
        System.out.println("当前状态:" + originator.getState());
        // 存储内部状态
        caretaker.saveMemento(originator.createMemento());
        System.out.println("存档");

        // 改变状态
        originator.setState("状态B");
        System.out.println("当前状态:" + originator.getState());

        // 改变状态
        originator.setState("状态C");
        System.out.println("当前状态:" + originator.getState());
        // 恢复状态
        originator.restoreMemento(caretaker.retrieveMemento());
        System.out.println("读档");

        System.out.println("恢复后状态:" + originator.getState());
    }
}

5.运行结果

当前状态:状态A
存档
当前状态:状态B
当前状态:状态C
读档
恢复后状态:状态A
黑箱实现

角色组成

MementoIF(备忘录角色): 空接口,不作任何实现。

Originator(发起人(原发器)角色): 记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。这里Memento做为原发器的私有内部类,来存储备忘录。备忘录只能由原发器对象来访问它内部的数据,原发器外部的对象不应该能访问到备忘录对象的内部数据。

Caretaker(备忘录负责人(管理者)角色): 对备忘录对象进行管理,但是不能对备忘录对象的内容进行操作或检查。

Memento 对象给 Originator 角色对象提供一个宽接口,而为其他对象提供一个窄接口,即为黑箱实现。

UML结构图

1.备忘录窄接口

public interface MementoIF {
}

2.接着创建一个原发器对象Originator,其中定义了一个私有化的内部类Memento实现了MementoIF接口,只有当前对象能访问

public class Originator {

    private String state;

    public String getState() {
        return state;
    }

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

    /**
     * 创建一个新的备忘录对象
     */
    public MementoIF createMemento() {
        return new Memento(state);
    }

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

    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;
        }
    }
}

3.创建备忘录管理者Caretaker,管理备忘录对象的

public class Caretaker {
    /**
     * 备忘录对象
     */
    private MementoIF memento;

    /**
     * 获取备忘录对象
     */
    public MementoIF retrieveMemento() {
        return memento;
    }

    /**
     * 保存备忘录对象
     */
    public void saveMemento(MementoIF memento) {
        this.memento = memento;
    }
}

4.创建测试类

public class Client {

    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        originator.setState("状态A");
        System.out.println("当前状态:" + originator.getState());
        // 改变状态
        originator.setState("状态B");
        System.out.println("当前状态:" + originator.getState());

        // 存储内部状态
        caretaker.saveMemento(originator.createMemento());
        System.out.println("存档");
        
        // 改变状态
        originator.setState("状态C");
        System.out.println("当前状态:" + originator.getState());
        // 恢复状态
        originator.restoreMemento(caretaker.retrieveMemento());
        System.out.println("读档");

        System.out.println("恢复后状态:" + originator.getState());
    }
}

5.运行结果

当前状态:状态A
当前状态:状态B
存档
当前状态:状态C
读档
恢复后状态:状态B

两者的代码结构是比较类似的,本质区别就是外部能不能访问备忘录的状态,备忘录角色具有安全等级;这里关于备忘录角色 -> 白箱实现利用的宽接口,黑箱模式利用的窄接口;

多重检查点

前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。 这种情况只需要使用有序队列方式可以很容易达到多重检查点

备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。

自述历史

所谓自述历史模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录 (Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在自述历史模式里面,发起人角色自己兼任负责人角色。
  
完整代码在GIT项目中

总结

关于使用备忘录的潜在代价:

  标准的备忘录模式的实现机制是依靠缓存来实现的,因此,当需要备忘的数据量较大时,或者是存储的备忘录对象数据量不大但是数量很多的时候,或者是用户很频繁的创建备忘录对象的时候,这些都会导致非常大的开销。

  因此在使用备忘录模式的时候,一定要好好思考应用的环境,如果使用的代价太高,就不要选用备忘录模式,可以采用其它的替代方案。

关于增量存储:

  如果需要频繁的创建备忘录对象,而且创建和应用备忘录对象来恢复状态的顺序是可控的,那么可以让备忘录进行增量存储,也就是备忘录可以仅仅存储原发器内部相对于上一次存储状态后的增量改变。

  比如:在命令模式实现可撤销命令的实现中,就可以使用备忘录来保存每个命令对应的状态,然后在撤销命令的时候,使用备忘录来恢复这些状态。由于命令的历史列表是按照命令操作的顺序来存放的,也是按照这个历史列表来进行取消和重做的,因此顺序是可控的。那么这种情况,还可以让备忘录对象只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态。

优点

给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。

实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点

消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

由于备份的信息是由发起人自己提供的,所以管理者无法预知备份的信息的大小,所以在客户端使用时,可能一个操作占用了很大的内存,但客户端并不知晓。

适用场景

需要保存/恢复数据的相关状态场景。

提供一个可回滚的操作。

备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高;

说点什么

参考文献:http://www.cnblogs.com/JsonShare/p/7283972.html

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter16/battcn-memento

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70734.html

相关文章

  • 练就Java24章真经—你所不知道的工厂方法

    摘要:用专业的话来讲设计模式是一套被反复使用多数人知晓的经过分类编目的代码设计经验的总结创建型模式,共五种工厂方法模式抽象工厂模式单例模式建造者模式原型模式。工厂方法模式的扩展性非常优秀。工厂方法模式是典型的解耦框架。 前言 最近一直在Java方向奋斗《终于,我还是下决心学Java后台了》,今天抽空开始学习Java的设计模式了。计划有时间就去学习,你这么有时间,还不来一起上车吗? 之所以要学...

    Chiclaim 评论0 收藏0
  • 【Vue原理】Vue源码阅读总结大会 - 序

    摘要:扎实基础幸好自己之前花了大力气去给自己打基础,让自己现在的基础还算不错。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Vue源码阅读总结大会 - 序 阅读源码是需...

    Edison 评论0 收藏0
  • 性能优化

    摘要:如果你的运行缓慢,你可以考虑是否能优化请求,减少对的操作,尽量少的操,或者牺牲其它的来换取性能。在认识描述这些核心元素的过程中,我们也会分享一些当我们构建的时候遵守的一些经验规则,一个应用应该保持健壮和高性能来维持竞争力。 一个开源的前端错误收集工具 frontend-tracker,你值得收藏~ 蒲公英团队最近开发了一款前端错误收集工具,名叫 frontend-tracker ,这款...

    liangzai_cool 评论0 收藏0
  • 设计模式在jdk中的应用

    摘要:本文只是寻找设计模式在中的应用。来补全这一块工厂模式中的应用包线程池解释和代码线程池中有线程创建工厂。状态模式中的应用解释和代码根据一个指针的状态而改变自己的行为适配器模式中的应用解释和代码将一个类的接口转换成客户希望的另外一个接口。 前言 最近重学设计模式,而且还有很多源码要看。所以就想一举两得。从源码中寻找设计模式。顺便还可以看看源码。。。本文只是寻找设计模式在java中的应用。优...

    dingding199389 评论0 收藏0

发表评论

0条评论

roland_reed

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<