备忘录模式

缘由

今天在看关于设计模式的文章的时候,偶然看到一个叫做“ 备忘录模式 ”的设计模式,感觉名字甚是有趣,便研究了一番。

开始学习

备忘录模式,顾名思义就是可以向备忘录一样支持记录过程并且可以查看到每一步的状态的一种设计模式。最常见的使用是一些美图软件里面,需要对图片处理的过程进行记录,用户可以回退到上一步,也可以前进到最新的一步。下面是类图:
类图

  • Originator 原发器:动作的发起者,负责创建一个备忘录来记录当前对象的内部状态,并可使用备忘录恢复内部状态。
  • Memento 备忘录:负责存储发起者对象的内部状态, 只有原发器可以创建备忘录并修改备忘录

  • Caretaker 负责人:用于管理备忘录,但是不能修改备忘录,只负责存储备忘录。

举个例子

我们在编写文章的时候,经常会有撤销和回退的需求,这里可以使用备忘录模式设计。为了满足多次撤销的功能,我们可以再负责人类里面持有一个备忘录的列表,这样就可以记录一条过程了。demo的效果图如下:
输入的撤销会回退demo
具体的类图如下: 文字输入的demo类图

  • ArticleMemento 文章备忘录类:用于对文章的状态进行保存。下面的代码省略了get、set方法。

    public class ArticleMemento {

            private String title = ""; //标题
            private String content = ""; //内容
            private int selection = 0; //指针的位置
    
            public ArticleMemento(ArticleOriginator originator) {
                this.title = originator.getTitle();
                this.content = originator.getContent();
                this.selection = originator.getSelection();
            }
     }
    
  • ArticleOriginator 文章的原发器,用于创建和修改文章备忘录。

    public class ArticleOriginator {

    private String title = ""; //标题
    private String content = ""; //内容
    private int selection = 0; //指针的位置
    
    public ArticleMemento createMemento() {
        return new ArticleMemento(this);
    }
    
    public ArticleOriginator setArticleMemento(ArticleMemento memento) {
        this.title = memento.getTitle();
        this.content = memento.getContent();
        this.selection = memento.getSelection();
        return this;
    }
    

    }

  • ArticleCaretaker 文章备忘录的负责人,管理备忘录历史记录。

    public class ArticleCaretaker {

    private List<ArticleMemento> mMementoList; //备忘录列表
    private int mMaxLength = 20; //记录的步数 ,限制记录的次数有助于减少内存使用
    private int index = 0; //记录当前的步数
    
    public ArticleCaretaker() {
        mMementoList = new ArrayList<>();
    }
    
    /**
     * 记录一次备忘
     *
     * @param memento
     */
    public void saveMemento(ArticleMemento memento) {
        //将index后面的删除掉
        List<ArticleMemento> removes = new ArrayList<>();
        for (int i = index + 1; i < mMementoList.size(); i++) {
            removes.add(mMementoList.get(i));
        }
        mMementoList.removeAll(removes);
        if (memento != null) {
            mMementoList.add(memento);
            //长度超过了最大值就需要将第一个备忘删除掉
            if (mMementoList.size() > mMaxLength) {
                mMementoList.remove(0);
            }
        }
    
        index = Math.max(0, mMementoList.size() - 1);
    }
    
    /**
     * 获取最后一步的备忘
     *
     * @return
     */
    public ArticleMemento undo() {
        index = Math.max(0, index - 1);
        if (mMementoList.size() > 1) {
            return mMementoList.get(index);
        } else return null;
    }
    
    /**
     * 恢复一个步骤
     *
     * @return
     */
    public ArticleMemento redo() {
        //没有记录返回空
        if (mMementoList.size() == 0) {
            return null;
        }
    
        index = Math.min(mMementoList.size() - 1, index + 1);
        return mMementoList.get(index);
    }
    

    }

使用

//在需要的地方创建ArticleCaretaker 和ArticleOriginator的对象,然后通过ArticleOriginator获取ArticleMemento对象保存在ArticleCaretaker。通过ArticleCaretaker来进行文章的撤销会回退功能
mArticleOriginator = new ArticleOriginator();
mArticleCaretaker = new ArticleCaretaker();
//修改文章的内容
mArticleOriginator
      .setContent(s.toString())
      .setSelection(s.length());
//保存记录
mArticleCaretaker.saveMemento(mArticleOriginator.createMemento());
//undo和redo
ArticleMemento memento = mArticleCaretaker.undo();
ArticleMemento memento = mArticleCaretaker.redo();

总结

备忘录模式适用于需要保存操作记录的地方,需要注意的地方是:
备忘录类只能在原发器类里面创建和修改,对其他的类保持封闭,负责人类只负责保存管理备忘录类,不需要知道备忘录类的具体内容和实现

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

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