行为篇-备忘录模式


前言

备忘录用来记录曾经发生过的事情,使回溯历史变得切实可行。备忘录模式(Memento)则可以在不破坏元对象封装性的前提下捕获其在某些时刻的内部状态,并像历史快照一样将它们保留在元对象之外,以备恢复之用。


提示:以下是本篇文章正文内容,下面案例可供参考

一、时光流逝

在计算机世界中,我们似乎可以来去自如,例如浏览器前进与后退、撤销文档修改、数据库备份与恢复、游戏存盘载入、操作系统快照恢复、手机恢复出厂设置等操作稀松平常。再深入到面向对象层面,我们知道当程序运行时一个对象的状态有可能随时发生变化,而当修改其状态时我们可以对其进行记录,如此便能够将对象恢复到任意记录的状态。备忘录模式正是采用这种理念,让历史重演。

二、 覆水难收

为了更生动地展现备忘录模式,以使读者更容易理解,我们来模拟这样一个场景:假设某作家要写一部科幻小说,当他构思完成后打开编辑器软件开始创作的时候,必然会创建一个文档。

1.文档类

那么我们首先来定义这个文档类Doc。

public class Doc {
    private String title;//文档标题
    private String body;//文档内容

    public Doc(String title) {
        this.title = title;//新建文档先命名
        this.body = "";//新建文档内容为空
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

2编辑器类

public class Editor {
    private Doc doc;//文档引用

    public Editor(Doc doc) {
        System.out.println("<<<打开文档" + doc.getTitle());
        this.doc = doc;//载入文档
        show();
    }

    public void append(String txt){
        System.out.println("<<<插入操作");
        doc.setBody(doc.getBody() +txt);
        show();
    }

    public void delete(){
        System.out.println("<<<删除操作");
        doc.setBody("");
        show();
    }

    public void save(){
        System.out.println("<<<存盘操作");
    }

    private void show(){//显示当前文档内容
        System.out.print(doc.getBody());
        System.out.println("文档结束>>>\n");
    }
}

要点:

  1. 我们保持以最简单的代码来模拟文档的编辑功能,开始依次有插入方法append()、删除方法delete()、存盘方法save(),以及显示文档内容方法show(),

3.客户端类

public class Client {
    public static void main(String[] args) {
        Editor editor = new Editor(new Doc("《英雄联盟之我是Faker》"));
        editor.append("第一章 混沌初开\n  正文两千字......\n");
        editor.append("第二章 荒漠之花\n  正文三千字......\n");
        editor.delete();//惨剧在此发生
    }
}
输出结果:
<<<打开文档《英雄联盟之我是Faker》
文档结束>>>

<<<插入操作
第一章 混沌初开
  正文两千字......
文档结束>>>

<<<插入操作
第一章 混沌初开
  正文两千字......
第二章 荒漠之花
  正文三千字......
文档结束>>>

<<<删除操作
文档结束>>>

说明:

  1. 作家开始创作并一口气写完了两章的内容,还没保存,就去洗个澡,然后被他的孩子不小心按了删除按钮,导致作家5000字的心血付之东流,不得不为自己的疏忽大意付出惨痛的代价。

三、破镜重圆

编辑器类提供的删除方法本来是出于软件功能的完整性而设计的,却反而给用户带来了潜在风险。所以,我们一定要避免发生这类误操作,才能带来更好的用户体验。大家一定想到了以Ctrl+Z组合键触发的撤销操作了吧。这条编辑器指令可以瞬间撤销用户的上一步操作并回退到上一个文档状态,这样不但给了用户吃后悔药的机会,还能省去用户频繁地进行存盘操作的麻烦。

1.历史快照类

这种自动备忘录机制是如何实现的呢?既然可以回溯历史,就一定得定义一个历史快照类,用来记录用户每步操作后的文档状态。

public class History {
    private String body;//用于备忘文档内容

    public History(String body) {
        this.body = body;
    }

    public String getBody() {
        return body;
    }
}

说明:

  1. 和文档类Doc非常类似,历史快照类History也是一个POJO类,它同样封装了属性“文档内容”。

2.重构文档类

public class Doc {
    private String title;//文档标题
    private String body;//文档内容

    public Doc(String title) {
        this.title = title;//新建文档先命名
        this.body = "";//新建文档内容为空
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public History createHistory(){
        return new History(this.body);
    }

    public void restoreHistory(History history){
        this.body = history.getBody();
    }
}

说明:

  1. 我们加入了创建历史记录方法createHistory(),它能够生成并返回当前文档内容对应的历史快照。与之相反,对应历史记录的恢复方法restoreHistory(),它能够根据传入的历史快照参数将文档内容恢复到任意历史时间点。

3.重构编辑器类

public class Editor {
    private Doc doc;//文档引用
    private List<History> historyRecords;//历史记录列表
    private int historyPosition = -1;//历史记录当前位置

    public Editor(Doc doc) {
        System.out.println("<<<打开文档" + doc.getTitle());
        this.doc = doc;//载入文档
        historyRecords = new ArrayList<>(); //初始化历史记录列表
        backUp();//载入文档后保存第一份历史记录
        show();
    }

    public void append(String txt){
        System.out.println("<<<插入操作");
        doc.setBody(doc.getBody() +txt);
        backUp();//添加后保存一份历史记录
        show();
    }

    public void delete(){
        System.out.println("<<<删除操作");
        doc.setBody("");
        backUp();//删除后保存一份历史记录
        show();
    }

    public void save(){
        System.out.println("<<<存盘操作"); //模拟存盘操作
    }

    private void show(){//显示当前文档内容
        System.out.print(doc.getBody());
        System.out.println("文档结束>>>\n");
    }

    private void backUp(){
        historyRecords.add(doc.createHistory());
        historyPosition++;
    }

    public void undo(){// 撤销操作:如按下组合键Ctrl+z
        System.out.println(">>>执行撤销操作");
        if(historyPosition == 0){
            return;// 不能再撤销了
        }
        historyPosition--;//历史记录位置回溯一次
        History history = historyRecords.get(historyPosition);
        doc.restoreHistory(history);//去除历史记录回溯一次
        show();
    }

    public void redo(){//重做操作
        //此处省略
    }
    
}

说明:

  1. 我们首先加入了一个历史记录列表historyRecords,我们可以把它当作一本有很多页的历史书,顺序记录着每个时间点发生的历史事件,它的当前页码体现整型定义的时间点索引historyPosition。
  2. 注意的备份方法backup(),它能将文档生成的快照加入历史记录列表historyRecords,做好历史的记录。
  3. 撤销方法undo()才能真正实现撤销操作。

4.客户端类

public class Client {
    public static void main(String[] args) {
        Editor editor = new Editor(new Doc("《英雄联盟之我是Faker》"));
        editor.append("第一章 混沌初开\n  正文两千字......\n");
        editor.append("第二章 荒漠之花\n  正文三千字......\n");
        editor.delete();
        editor.undo();
        editor.undo();
    }
}
输出结果:
<<<打开文档《英雄联盟之我是Faker》
文档结束>>>

<<<插入操作
第一章 混沌初开
  正文两千字......
文档结束>>>

<<<插入操作
第一章 混沌初开
  正文两千字......
第二章 荒漠之花
  正文三千字......
文档结束>>>

<<<删除操作
文档结束>>>

>>>执行撤销操作
第一章 混沌初开
  正文两千字......
第二章 荒漠之花
  正文三千字......
文档结束>>>

>>>执行撤销操作
第一章 混沌初开
  正文两千字......
文档结束>>>

说明:

  1. 如代码示,作家又一口气写了两章内容。假设小孩又对文档进行了误删除操作,就可以从容不迫地按下Ctrl+Z组合键,以此触发编辑器的撤销方法undo(),接着可以清楚地看到输出中5000字内容被奇迹般地恢复如初。

总结

提示:这里对文章进行总结:

  1. 备忘录模式就像一台时光机,让我们在软件世界里自由自在地进行时空穿梭。需要注意的是,备忘录类一定独立于元数据类而单独成类,其生成的历史记录也应该在元数据类之外进行维护,这样不但确保了元数据类的封装不被破坏,而且实现了对其内部状态历史变化的捕获与恢复。
  2. 备忘录模式的各角色定义如下:
  • Originator(元):状态需要被记录的元对象类,其状态是随时可变的。既可以生成包含其内部状态的即时备忘录,也可以利用传入的备忘录恢复到对应状态。对应本章例程中的文档类Doc。
  • Memento(备忘录):与元对象相仿,但只需要保留元对象的状态,一个状态对应一个备忘录对象。对应本章例程中的历史快照类History。
  • CareTaker(看护人):历史记录的维护者,持有所有记录的历史记录,并且提供对元数据对象的恢复操作,如撤销undo()、重做redo()等,一般不提供对历史记录的修改。对应本章例程中的编辑器类Editor。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值