16、备忘录模式(Memento Pattern)

1. 备忘录模式

1.1 简介

  备忘录模式是一种软件设计模式,它提供一种能将一个对象恢复到旧状态的能力(回滚式的撤销操作)。备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。Memento Pattern在不破坏封闭的前提下,捕获并外部化一个对象的内部状态,这样之后就可以将该对象恢复到保存时的状态。

  在应用软件的开发过程中,很多时候我们都需要记录一个对象的内部状态。在具体实现过程中,为了允许用户取消不确定的操作或从错误中恢复过来,需要实现备份点和撤销机制,而要实现这些机制,必须事先将状态信息保存在某处,这样才能将对象恢复到它们原先的状态。备忘录模式是一种给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。

  备忘录模式通过三个对象来实现:originator、caretaker和memento。originator是一些拥有内部状态的对象。caretaker将在originator之上进行一些处理,但是同时希望能够撤销之前的处理操作。caretaker首先向originator请求一个memento对象,然后它将进行它所要进行的任何操作。为了回滚回进行这些操作的状态,caretaker将返回memento对象给originator。memento对象是一种不透明对象(caretaker不能也不应该对其改变)。使用这种模式的时候,需要注意的是originator可能改变其他对象或资源的状态,备忘录模式只对单一对象起作用。

1.2 Memento Pattern的uml

Memento uml.png

Memento Pattern的角色:

  • Memento:
    备忘录,用于存储originator对象的内部状态。memento对象应该根据originator的实际需要,存储必要的originator对象的内部状态。它能够保护对状态的访问。memento实际上拥有两个接口。caretaker对象只能访问memento对象的窄接口,即它只能够把memento对象传递给其他对象。相反,originator对象可以访问memento的宽接口,即获取所有能恢复到旧状态的必要数据。理想情况下,只有创建memento的oiginator对象才有权限访问memento对象的内部状态信息.

  • Originator:
    发起人。创建一个memento对象来存储它当前内部状态的一个快照,这些内部信息将保存在memento对象之中。

  • Caretaker:
    管理者,负责memento对象的安全存储,但从不对memento对象的内容进行操作或检查。

2. Memento Pattern的示例

  我们以游戏为示例,游戏角色拥有生命值和经验值,每一次打怪升级都需要消耗生命值和经验值,冷却足够时间后都会恢复。

** GamePlayer:**

// Originator
public class GamePlayer {

    // 遊戲角色的生命值
    private int mHp;

    // 遊戲角色的經驗值
    private int mExp;

    public GamePlayer(int hp, int exp)
    {
        mHp = hp;
        mExp = exp;
    }

    public GameMemento saveToMemento()
    {
        return new GameMemento(mHp, mExp);
    }

    public void restoreFromMemento(GameMemento memento)
    {
        mHp = memento.getGameHp();
        mExp = memento.getGameExp();
    }

    public void play(int hp, int exp)
    {
        mHp = mHp - hp;
        mExp = mExp + exp;
    }
}

** GameMemento:**

// Memento
public class GameMemento {

    // 假設只有這兩個資料要保留
    private int mGameHp;
    private int mGameExp;

    public GameMemento(int hp, int exp)
    {
        mGameHp = hp;
        mGameExp = exp;
    }

    public int getGameHp()
    {
        return mGameHp;
    }

    public int getGameExp()
    {
        return mGameExp;
    }
}

** Caretaker:**

// Caretaker
public class GameCaretaker {

    // 保留要處理的資料。
    // 這邊只是範例,所以 Caretaker
    // 只能處理一個 Memento。
    // 實務上當然可以用更複雜的結構來
    // 處理多個 Memento,如 ArrayList。
    private GameMemento mMemento;

    public GameMemento getMemento()
    {
        return mMemento;
    }

    public void setMemento(GameMemento memento)
    {
        mMemento = memento;
    }
}

** 客户端调用示例:**

 public static void main(String[] args)
    {
        // 創造一個遊戲角色
        GamePlayer player = new GamePlayer(100, 0);

        // 先存個檔
        GameCaretaker caretaker = new GameCaretaker();
        caretaker.setMemento(player.seveToMemento());

        // 不小心死掉啦
        player.play(-100, 10);

        // 重新讀取存檔,又是一尾活龍
        player.restoreFromMemento(caretaker.getMemento());
    }

3. 总结

备忘录模式的优点:

  • 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原。
  • 实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码改动,这种模式简化了原发器对象,备忘录只保存原发器的状态,采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在负责人中定义集合对象来存储多个备忘录。

备忘录模式的缺点:

  • 资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存,而且每保存一次对象的状态都需要消耗内存资源,如果知道这一点大家就容易理解为什么一些提供了撤销功能的软件在运行时所需的内存和硬盘空间比较大了。

备忘录模式适用环境:

  • 保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态。
  • 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态

备忘录模式应用:

  • 几乎所有的文字或者图像编辑软件都提供了撤销(Ctrl+Z)的功能,即撤销操作,但是当软件关闭再打开时不能再进行撤销操作,也就是说不能再回到关闭软件前的状态,实际上这中间就使用到了备忘录模式,在编辑文件的同时可以保存一些内部状态,这些状态在软件关闭时从内存销毁,当然这些状态的保存也不是无限的,很多软件只提供有限次的撤销操作。
  • 数据库管理系统DBMS所提供的事务管理应用了备忘录模式,当数据库某事务中一条数据操作语句执行失败时,整个事务将进行回滚操作,系统回到事务执行之前的状态。

备忘录模式扩展:
备忘录的封装性

  • 为了确保备忘录的封装性,除了原发器外,其他类是不能也不应该访问备忘录类的,在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要分享信息而不让其他类知道,实现的方法因编程语言的不同而不同。
  • C++可以用friend关键字,使原发器类和备忘录类成为友元类,互相之间可以访问对象的一些私有的属性;
  • 在Java语言中可以将两个类放在一个包中,使它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据。
    多备份实现
  • 在负责人中定义一个集合对象来存储多个状态,而且可以方便地返回到某一历史状态。
  • 在备份对象时可以做一些记号,这些记号称为检查点(Check Point)。在使用HashMap等实现时可以使用Key来设置检查点。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352