关注「Java视界」公众号,获取更多技术干货

备忘录模式Memento——给你的对象一剂“后悔药”

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

一、备忘录模式

我们编辑word文档、写这篇博客的时候,经常用到 ctrl+z 这个快捷键,就是撤销当前操作回到上一步编辑状态。还有一些线上考试系统,在考到中间的时候退出,再重进还能在上一次的基础上接着做题。这里面都有一个环节,那就是回复到之前的某个节点,这个怎么实现的呢?

其中一种方式就是通过备忘录模式去实现,它能保存对象当前状态,并在之后恢复到此状态(后悔药),当然它也保证被保存的对象状态不能被外部访问,保证内部完整性,不向外透露。

角色

  • 发起人: 发起人的内部要规定要备忘的范围,负责提供备案数据

  • 备忘录: 存储发起人对象的内部状态,在需要的时候,可以向其他人提供这个内部状态,以方便负责人恢复发起人状态

  • 负责人: 负责对备忘录进行管理(保存或提供备忘录)

二、代码实现

这里就以考试系统为例,中途退出再进入。

Originator 创建者:

/**
 * Originator 创建者
 * 其内部可以实现备忘录的创建以及恢复
 * 负责创建一个备忘录,用来记录、恢复自身的内部状态。同时根据需要决定Memento存储自身的那些状态
 *
 * 考试系统(Originator)
 */
@ToString
public class ExaminationSystem {
    // 题目序号
    private int topicNum = 1;
    // 剩余时间
    private int remainingTime = 120;
    // 答案缓存
    private String answer = "";

    // 开始答题
    public void startTest(){
        Console.log("开始答题:" + String.format("第%d题", topicNum));
        remainingTime -=10;
        Console.log("时间还剩" + remainingTime + "分钟");
        answer = "答案1";
        topicNum++;
        Console.log("开始做"+String.format("第%d题", topicNum));
    }

    // 退出考试
    public void quit(){
        Console.log("---------------------");
        Console.log("退出前的情况:"+this.toString());
        Console.log("退出考试");
        Console.log("---------------------");
    }

    // 创建备忘录
    public Memento createMemoto(){
        Memento memento = new Memento();
        memento.topicNum = topicNum;
        memento.remainingTime = remainingTime;
        memento.answer = answer;
        return memento;
    }

    // 获取备忘录,恢复当时的对象状态
    public void restore(Memento memento){
        this.topicNum = memento.topicNum;
        this.remainingTime = memento.remainingTime;
        this.answer = memento.answer;
        System.out.println("恢复后的考试属性:"+this.toString());
    }
}

Memento 备忘录类:

/**
 * 备忘录类
 */
@ToString
public class Memento {
    public int topicNum;
    public int remainingTime;
    public String answer;
}
/**
 * 负责管理 Memento
 */
public class Caretaker {
    //备忘录
    Memento memento;

    //存档
    public void archive(Memento memoto){
        this.memento = memoto;
    }

    //获取存档
    public Memento getMemoto(){
        return memento;
    }
}
public class Client {
    public static void main(String[] args) {
        // 构建考试系统对象(Originator)
        ExaminationSystem examination = new ExaminationSystem();
        //开始考试
        examination.startTest();
        //构建caretaker,用于试卷存档
        Caretaker caretaker = new Caretaker();
        //通过考试系统本身创建备忘录,caretaker执行存档操作
        caretaker.archive(examination.createMemoto());
        //退出考试
        examination.quit();
        //重新开启考试,并通过caretaker恢复考试进度
        ExaminationSystem examination_new = new ExaminationSystem();
        examination_new.restore(caretaker.getMemoto());
    }
}
开始答题:第1题
时间还剩110分钟
开始做第2题
---------------------
退出前的情况:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)
退出考试
---------------------
恢复后的考试属性:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)

三、适用场景

上面的介绍可以看到备忘录模式可以保存一份对象的快照,一般来说,一个对象有一个对应的备忘对象,记录对象中要备忘的字段,而多个对象的备忘,同一由同一个负责人进行管理,可以用 Map 来做到这一点,负责人中的备忘容器是一个 Map 类型的数据,值是一个实现备忘接口的数据结构即可。

有了这个快照那就等于有了“后悔药”,比如可以暂时将游戏存档,可以撤销操作回到之前的状态,再比如把保存的备忘录序列化成字符串保存在磁盘中,下次启动的时候从磁盘中获取状态,这样就可以做一个简单的快照了,等等。

另外要注意一下备忘模式的优缺点:

  • 优点:

    • 备忘录模式仅做数据备忘,不论该数据是否正确。
    • 设计模式最大的优点就是解耦,各司其职,发起人只需要提供备忘数据,不需要对其进行管理
  • 缺点

    • 实际应用中,备忘录模式大多是多状态的,如果进行大量备忘的话,会占用大量内存,当然,如果持久化在磁盘中的话,会减少内存占用,但会增加IO操作,这就需要开发者根据实际业务情况进行取舍了。

使用场景

  • 需要保存一个对象在某一个时刻的状态或者部分状态

  • 如果用一个接口来让其他对象的到这些状态,将会暴露这个对象的实现细节并破坏对象的的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态

 

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(38)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货