Memento模式——保存对象状态

我们在使用文本编辑器编写文件时,如果不小心删除了某句话,可以通过撤销功能将文件恢复到之前的状态。

使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。

要想恢复实例,需要一个自由访问实例内部结构的权限。但是,如果稍不注意,又可能会将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护。这种情况就叫做“破坏了封装性”。

通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。这就是Memento模式

使用Memento模式可以实现应用程序的以下功能。

  • Undo(撤销)

  • Redo(重做)

  • History(历史记录)

  • Snapshot(快照)

下面的示例程序是一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下。

  • 游戏是自动进行的

  • 游戏的主人公通过掷骰子来决定下一个状态

  • 当骰子的点数为1的时候,主人公的金钱会增加

  • 当骰子的点数为2的时候,主人公的金钱会减少

  • 当骰子的点数为6的时候,主人公会得到水果

  • 主人公没有钱时游戏就会结束

在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在的状态保存起来,所保存的数据为当前所持有的金钱和水果。如果不断掷出了会导致金钱减少的点数,为了防止金钱变为0而结束游戏,我们会使用Memento
的实例将游戏恢复至之前的状态。

下面是示例程序的具体实现。

  • 类的一览表
名字 说明
game Memento 表示Gamers状态的类
game Gamer 表示游戏主人公的类。它会生成Memento的实例
default Main 进行游戏的类。它会事先保存Memento的实例,之后会根据需要恢复Gamer的状态
  • Memento类
package game;import java.util.ArrayList;
import java.util.List;public class Memento {int money;ArrayList fruits;public int getMoney() {return money;}Memento(int money) {this.money = money;this.fruits = new ArrayList();}void addFruit(String fruit) {fruits.add(fruit);}List getFruits() {return (List) fruits.clone();}
}
  • Gamer类
package game;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;public class Gamer {private int money;private List fruits = new ArrayList();private Random random = new Random();private static String[] fruitsname = {"苹果", "葡萄", "香蕉", "橘子"};public Gamer(int money) {this.money = money;}public int getMoney() {return money;}public void bet() {int dice = random.nextInt(6) + 1;if (dice == 1) {money += 100;System.out.println("所持金钱增加了。");} else if (dice == 2) {money /= 2;System.out.println("所持金钱减半了。");} else if (dice == 6) {String f = getFruit();System.out.println("获得了水果(" + f + ")。");fruits.add(f);} else {System.out.println("什么都没有发生。");}}public Memento createMemento() {Memento m = new Memento(money);Iterator it = fruits.iterator();while (it.hasNext()) {String f = (String) it.next();if (f.startsWith("好吃的")) {m.addFruit(f);}}return m;}public void restoreMemento(Memento memento) {this.money = memento.money;this.fruits = memento.getFruits();}public String toString() {return "[money = " + money + ", fruits = " + fruits + "]";}private String getFruit() {String prefix = "";if (random.nextBoolean()) {prefix = "好吃的";}return prefix + fruitsname[random.nextInt(fruitsname.length)];}
}
  • Main类
import game.Gamer;
import game.Memento;public class Main {public static void main(String[] args) {Gamer gamer = new Gamer(100);Memento memento = gamer.createMemento();for (int i = 0; i < 10; i++) {System.out.println("===" + i);System.out.println("当前状态:" + gamer);gamer.bet();    // 进行游戏System.out.println("所持金钱为" + gamer.getMoney() + "元。");// 决定如何处理Mementoif (gamer.getMoney() > memento.getMoney()) {System.out.println("    (所持金钱增加了许多," +"因此保存游戏当前的状态)");memento = gamer.createMemento();} else if (gamer.getMoney() < memento.getMoney() / 2) {System.out.println("    (所持金钱减少了许多," +"因此将游戏恢复至以前的状态)");gamer.restoreMemento(memento);}// 等待一段时间try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.println("");}}
}

这里值得注意的是,Memento类中有一些字段和方法不是public,也不是private的,因为无法从game包外部生成Memento实例或修改Memento的状态,但是同一包内的Gamer类可以访问Memento类。

示例输出如下:

===0
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。===1
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。===2
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。(所持金钱增加了许多,因此保存游戏当前的状态)===3
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。===4
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。===5
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。===6
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。===7
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。===8
当前状态:[money = 100, fruits = []]
所持金钱减半了。
所持金钱为50元。(所持金钱减少了许多,因此将游戏恢复至以前的状态)===9
当前状态:[money = 200, fruits = []]
所持金钱增加了。
所持金钱为300元。(所持金钱增加了许多,因此保存游戏当前的状态)

##Memento模式中的角色

  • Originator(生成者)

Originator角色会在保存自己的最新状态生成Memento角色。当把以前保存的Memento角色传递给Originator角色时,它会恢复至生成该Memento角色时的状态。在示例程序中,由Gamer类扮演此角色。

  • Memento(纪念品)

Memento角色会将Originator角色的内部信息整合在一起。在Memento角色中虽然保存了Originator角色的信息,但它不会向外部公开这些信息。

Memento角色有以下两种接口(API)

  • wide interface——宽接口(API)

Memento角色提供的“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator角色。

  • narrow interface——窄接口(API)

Memento角色为外部的Caretaker角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露。

通过对外提供以上两种接口(API),可以有效地防止对象的封装性被破坏。

在示例程序中,由Memento类扮演此角色。

Originator角色和Memento角色之间是强关联关系。

  • Caretaker(负责人)

当Caretaker角色想要保存当前的Originator角色的状态时,会通知Originator角色。Originator角色在接收到通知后会生成Memento角色的实例并将其返回给Caretaker角色。由于以后可能会用到Memento
实例来将Originator恢复至原来的状态,因此Caretaker角色会一直保存Memento实例。在示例程序中,由Main类扮演此角色。

不过,Caretaker角色只能使用Memento角色的窄接口(API),也就是说它无法访问Memento角色内部的所有信息。它只是将Originator角色生成的Memento角色当做一个黑盒子保存起来。

虽然Originator角色和Memento角色之间是强关联关系,但Caretaker角色和Memento角色之间是弱关联关系。Memento角色对Caretaker角色隐藏了自身的内部信息。


##Memento模式的思路

Caretaker角色的职责是决定何时拍摄快照,何时撤销以及保存Memento角色。

另一方面,Originator角色的职责是生成Memento角色和-使用接收到的Memento角色来恢复自己的状态。

以上就是Caretaker角色与Originator角色的职责分担。有了这样的职责分担,当我们需要对应一下需求变更时,就可以完全不用修改Originator角色。

  • 变更为可以多次撤销

  • 变更为不仅可以撤销,还可以将现在的状态保存在文件中


想了解更多关于设计模式:设计模式专栏
设计模式总结

设计模式:Memento模式相关推荐

  1. memento模式_Java中的Memento设计模式-示例教程

    memento模式 记忆模式是行为设计模式之一 . 当我们要保存对象的状态以便以后可以恢复时,可以使用Memento设计模式. 使用Memento模式以这种方式实现该目的,即无法在对象外部访问对象的已 ...

  2. 设计模式学习笔记——备忘录(Memento)模式

    设计模式学习笔记--备忘录(Memento)模式 @(设计模式)[设计模式, 备忘录模式, memento] 设计模式学习笔记备忘录Memento模式 基本介绍 备忘录案例 类图 实现代码 Memen ...

  3. 设计模式---状态变化模式之备忘录模式(Memento)

    一:概念 用于保存对象的内部状态,并在需要的时候(undo/rollback)回复对象以前的状态 二:应用场景 如果一个对象需要保存状态并可通过undo或rollback等操作恢复到以前的状态时,可以 ...

  4. memento模式_Java中的Memento设计模式

    memento模式 Memento design pattern is one of the behavioral design pattern. Memento design pattern is ...

  5. 设计模式-备忘录模式(Memento)-Java

    设计模式-备忘录模式(Memento)-Java 目录 文章目录 1.前言 2.示例案例-可悔棋的中国象棋 3.备忘录模式概述 3.1.备忘录模式定义 3.2.备忘录模式结构 3.3.备忘录模式结构图 ...

  6. C++设计模式——备忘录模式(memento pattern)

    博主看了许多文章和一些书,发现要么代码不全,要么对备忘录的理解有偏差,要么干脆根本就不是备忘录模式,经过博主查阅十余篇文章和详细研究书本备忘录模式,总结出这篇精华的c++备忘录模式文章,感兴趣的朋友可 ...

  7. memento模式_Memento设计模式概述

    memento模式 Memento设计模式是一种软件设计模式,用于将对象回滚到其先前状态. 它是行为设计​​模式的一部分,与算法和对象之间的职责分配有关. 行为模式描述了处理对象或类之间的通信的模式. ...

  8. 设计模式【17】——备忘录模式(Memento 模式)

    文章目录 前言 一.备忘录模式(Memento 模式) 二.具体源码 1.Memento.h 2.Subject.cpp 3.main.cpp 三.运行结果 总结 前言 备忘录模式用于保存和恢复对象的 ...

  9. 设计模式--备忘录(Memento)模式

    模式定义 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态 类图 要点总结 备忘录(Memento)存储原发器(Originato ...

最新文章

  1. 烽火18台系列之十一:刚需中的刚需——网站篡改监控
  2. qt快速加载图片_Qt实用技巧:使用Qt加载超大图片的耗时测试
  3. RocketMQ中的主从复制
  4. 卸载mysql 服务
  5. 5g存储服务器是什么项目,5G时代对服务器有什么要求?
  6. hdu4292Food(最大流Dinic算法)
  7. USACO Section 4.2 Drainage Ditches(最大流)
  8. 【docker】为docker下的php容器安装php-redis扩展【编译安装】
  9. 【Oracle AWR详解分析-02】
  10. route debugger
  11. 如何让你的Linux云服务器更加的安全?
  12. python3.9出了吗_Python 3.9正式版,新特性提前一睹为快
  13. IDEA maven的安装与配置(超详细)
  14. java控制zebra打印机_从Zebra打印机读取状态
  15. ARM 汇编语言教程
  16. 软件项目管理的常见问题
  17. ComposeOptions.kotlinCompilerVersion is deprecated
  18. 计算机在地理数据的应用,地理信息系统(GIS)在环境监测中的应用
  19. 判断对象内的 属性是否为空
  20. ligerui 表格中设置单元格不可编辑,添加行,删除行

热门文章

  1. 在HTML中rotate属性,Css3属性RotateX的使用
  2. OleDbDataAdapter.update更新数据成功,但数据没有变化
  3. 怎么添加桌面计算机快捷键,win10中怎么给计算器添加快捷键
  4. python wraps
  5. 一个女孩的IT创业经历连载(一)
  6. 五个维度打造研发管理体系【原创】
  7. 什么是A股什么是B股,为什么有AB股之分?
  8. Maven配置完毕后构建失败,无法下载JAR包,输入mvn help:system后出现No plugin found for prefix ‘help‘...问题的解决方案
  9. Excel工作表目录以及链接
  10. 大学生在学校有没有什么能赚钱的方法?校园跑腿外卖取快递平台项目介绍