[转]深入浅出Java设计模式之备忘录模式

本文转自:http://dev.yesky.com/450/2070450.shtml

 一、引子

  俗话说:世上难买后悔药。所以凡事讲究个“三思而后行”,但总常见有人做“痛心疾首”状:当初我要是……。如果真的有《大话西游》中能时光倒流的“月光宝盒”,那这世上也许会少一些伤感与后悔——当然这只能是痴人说梦了。

  但是在我们手指下的程序世界里,却有的后悔药买。今天我们要讲的备忘录模式便是程序世界里的“月光宝盒”。

  二、定义与结构

  备忘录(Memento)模式又称标记(Token)模式。GOF给备忘录模式的定义为:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

  在讲命令模式的时候,我们曾经提到利用中间的命令角色可以实现undo、redo的功能。从定义可以看出备忘录模式是专门来存放对象历史状态的,这对于很好的实现undo、redo功能有很大的帮助。所以在命令模式中undo、redo功能可以配合备忘录模式来实现。

  其实单就实现保存一个对象在某一时刻的状态的功能,还是很简单的——将对象中要保存的属性放到一个专门管理备份的对象中,需要的时候则调用约定好的方法将备份的属性放回到原来的对象中去。但是你要好好看看为了能让你的备份对象访问到原对象中的属性,是否意味着你就要全部公开或者包内公开对象原本私有的属性呢?如果你的做法已经破坏了封装,那么就要考虑重构一下了。

  备忘录模式只是GOF对“恢复对象某时的原有状态”这一问题提出的通用方案。因此在如何保持封装性上——由于受到语言特性等因素的影响,备忘录模式并没有详细描述,只是基于C++阐述了思路。那么基于Java的应用应该怎样来保持封装呢?我们将在实现一节里面讨论。

  来看下“月光宝盒”备忘录模式的组成部分:

  1) 备忘录(Memento)角色:备忘录角色存储“备忘发起角色”的内部状态。“备忘发起角色”根据需要决定备忘录角色存储“备忘发起角色”的哪些内部状态。为了防止“备忘发起角色”以外的其他对象访问备忘录。备忘录实际上有两个接口,“备忘录管理者角色”只能看到备忘录提供的窄接口——对于备忘录角色中存放的属性是不可见的。“备忘发起角色”则能够看到一个宽接口——能够得到自己放入备忘录角色中属性。

  2) 备忘发起(Originator)角色:“备忘发起角色”创建一个备忘录,用以记录当前时刻它的内部状态。在需要时使用备忘录恢复内部状态。

  3) 备忘录管理者(Caretaker)角色:负责保存好备忘录。不能对备忘录的内容进行操作或检查。

  备忘录模式的类图真是再简单不过了:


  三、举例

  按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。而对于其他角色则是不可见的。GOF在书中以C++为例进行了探讨。但是在Java中没有提供类似于C++中友元的概念。在Java中怎样才能保持备忘录角色的封装呢?

  下面对三种在Java中可保存封装的方法进行探讨。

  第一种就是采用两个不同的接口类来限制访问权限。这两个接口类中,一个提供比较完备的操作状态的方法,我们称它为宽接口;而另一个则可以只是一个标示,我们称它为窄接口。备忘录角色要实现这两个接口类。这样对于“备忘发起角色”采用宽接口进行访问,而对于其他的角色或者对象则采用窄接口进行访问。

  这种实现比较简单,但是需要人为的进行规范约束——而这往往是没有力度的。

  第二种方法便很好的解决了第一种的缺陷:采用内部类来控制访问权限。将备忘录角色作为“备忘发起角色”的一个私有内部类。好处我不详细解释了,看看代码吧就明白了。下面的代码是一个完整的备忘录模式的教学程序。它便采用了第二种方法来实现备忘录模式。

  还有一点值得指出的是,在下面的代码中,对于客户程序来说“备忘录管理者角色”是不可见的,这样简化了客户程序使用备忘录模式的难度。下面采用“备忘发起角色”来调用访问“备忘录管理者角色”,也可以参考门面模式在客户程序与备忘录角色之间添加一个门面角色。

 class Originator{

  //这个是要保存的状态
  private int state= 90;
  //保持一个“备忘录管理者角色”的对象
  private Caretaker c = new Caretaker();
  //读取备忘录角色以恢复以前的状态
  public void setMemento(){
   Memento memento = (Memento)c.getMemento();
   state = memento.getState();
   System.out.println("the state is "+state+" now");
  }
  //创建一个备忘录角色,并将当前状态属性存入,托给“备忘录管理者角色”存放。

  public void createMemento(){
   c.saveMemento(new Memento(state));
  }
  //this is other business methods...
  //they maybe modify the attribute state

  public void modifyState4Test(int m){
   state = m;
   System.out.println("the state is "+state+" now");
  }

  //作为私有内部类的备忘录角色,它实现了窄接口,可以看到在第二种方法中宽接口已经不再需要
  //注意:里面的属性和方法都是私有的

  private class Memento implements MementoIF{
   private int state ;
   private Memento(int state){
    this.state = state ;
   }

   private int getState(){
    return state;
   }
  }
 }

 //测试代码——客户程序

 public class TestInnerClass{
  public static void main(String[] args){
   Originator o = new Originator();
   o.createMemento();
   o.modifyState4Test(80);
   o.setMemento();
  }
 }

 //窄接口

 interface MementoIF{}

 //“备忘录管理者角色”

 class Caretaker{
  private MementoIF m ;
  public void saveMemento(MementoIF m){
   this.m = m;
  }
  public MementoIF getMemento(){
   return m;
  }
 }

  第三种方式是不太推荐使用的:使用clone方法来简化备忘录模式。由于Java提供了clone机制,这使得复制一个对象变得轻松起来。使用了clone机制的备忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是在为你的类实现clone方法时要慎重啊。

  在上面的教学代码中,我们简单的模拟了备忘录模式的整个流程。在实际应用中,我们往往需要保存大量“备忘发起角色”的历史状态。这时就要对我们的“备忘录管理者角色”进行改造,最简单的方式就是采用容器来按照顺序存放备忘录角色。这样就可以很好的实现undo、redo功能了。

  四、适用情况

  从上面的讨论可以看出,使用了备忘录模式来实现保存对象的历史状态可以有效地保持封装边界。使用备忘录可以避免暴露一些只应由“备忘发起角色”管理却又必须存储在“备忘发起角色”之外的信息。把“备忘发起角色”内部信息对其他对象屏蔽起来, 从而保持了封装边界。

  但是如果备份的“备忘发起角色”存在大量的信息或者创建、恢复操作非常频繁,则可能造成很大的开销。

  GOF在《设计模式》中总结了使用备忘录模式的前提:

  1) 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。

  2) 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

  五、总结

  介绍了怎样来使用备忘录模式实现存储对象历史状态的功能,并对基于Java的实现进行了讨论。欢迎大家指正。

转载于:https://www.cnblogs.com/freeliver54/archive/2012/10/16/2725604.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379220.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

递归问题(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; main.c #include <stdio.h>//该程序使用递归将字符串从后往前依次输出void reverse(char* s) {if( (s ! NULL) && (*s ! \0) ){reverse(s 1);printf("%c", *s);…

递归-裴波那契数列(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; main.c #include <stdio.h>//该程序输出裴波那契数列 int fibonacci(int n) {if( n > 1 ){return fibonacci(n-1) fibonacci(n-2);//注意&#xff1a;这里调用是一直调用左边函…

经典例题(一)

1&#xff0c;已知复数 x 6 8j 请写出它的模、实部、虚部及共轭复数的命令&#xff0c;并写出运行结果。 X 6 8j print("模为:%d"% abs(X)) print("实部为:%s"% X.real) print("虚部为:%s"% X.imag) print("共轭复数为:%s"% X.co…

递归-汉诺塔(代码、分析、汇编)

代码&#xff1a; #include <stdio.h>void hanoi(int n, char a, char b, char c) {if( n > 0 ){if( n 1 ){printf("%c -> %c\n", a, c);}else{hanoi(n-1, a, c, b);printf("%c -> %c\n", a, c);hanoi(n-1, b, a, c);}} }int main() {han…

if语句(四)

1&#xff0c;简单if示例 phones [iphone,xiaomi,huawei,smartisan] for phone in phones:if phone huawei:print(phone.upper())#将字符串的所有字母大写else:print(phone.title())#将字符串中的每个单词的首字符大写效果图如下&#xff1a; 2&#xff0c;if条件测试 ph…

welcome to my blog

转载于:https://www.cnblogs.com/jiangjun/archive/2012/10/22/2734600.html

递归-输出字符串所有的组合情况(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; #include <stdio.h>/*程序描述&#xff1a;输出字符串所有的组合情况使用permutation函数进行将指定的下标值&#xff0c;与最大下标值这个范围的每个下标值进行交换每调用一次permu…

递归-计算字符串长度(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; main.c #include <stdio.h>//该程序用递归计算字符串长度int strlen(const char* s) {if( s NULL ){return -1;}else if( *s \0 ){return 0;}else{return strlen(s1) 1;} }int m…

Python-杨辉三角

在控制台输出如图所示一个8层的杨辉三角。 杨辉三角介绍&#xff1a; 每个数等于它上方两数之和 每行数字左右对称&#xff0c;由1开始逐渐变大 第n行的数字有n项&#xff0c;将n取8 def yanghui(n):l[1,1]for x in range(1,n):for a in range(x):l[a]l[a]l[a1]l.insert(0,1)…

如何向妻子解释OOD(转)

前言 此文译自CodeProject上<How I explained OOD to my wife>一文&#xff0c;该文章在Top Articles上排名第3&#xff0c;读了之后觉得非常好&#xff0c;就翻译出来&#xff0c;供不想读英文的同学参考学习。 作者(Shubho)的妻子(Farhana)打算重新做一名软件工程师(她…

不安全代码和指针资料汇编

不安全代码和指针&#xff08;C# 编程指南&#xff09;为了保持类型安全&#xff0c;默认情况下&#xff0c;C# 不支持指针运算。不过&#xff0c;通过使用 unsafe 关键字&#xff0c;可以定义可使用指针的不安全上下文。有关指针的更多信息&#xff0c;请参见主题指针类型。 注…

ffmpeg-从flv文件中提取AAC音频数据保存为文件

AAC ADTS格式协议&#xff1a; 从flv文件中提取AAC音频数据保存为文件。 如果需要详细了解AAC ADTS格式&#xff0c;可以查询文档。 原文件&#xff1a; 提取aac文件&#xff1a; main.c #include <stdio.h> #include <libavutil/log.h>> #include <lib…

N Queen(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; main.c #include <stdio.h>/* 程序描述&#xff1a;输出N*N中符合左右对角线与上下左右方向都没被使用的位置在每一行的所有情况使用检测左上角&#xff0c;正上角&#xff0c;右上…

Python-身份证核对

中华人民共和国居民身份证号码由17 位数字和1位校验码组成。其中&#xff0c;前6位为所在地编号&#xff0c;第7~14 位为出生年月日&#xff0c;第15~17位为登记流水号&#xff0c;其中第17位偶数为女性&#xff0c;奇数为男性。校验码的生成规则如下: 将前面的身份证号码17位数…

树存储结构(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; LinkList.h LinkList.c 线性表 GTree.h #ifndef _GTREE_H_ #define _GTREE_H_typedef void GTree;//定义树类型 typedef void GTreeData;//定义节点中存放数据的类型 typedef void (GTre…

二元矩阵峰值搜索_好斗的牛(二元搜索)

二元矩阵峰值搜索A farmer has built a long barn with N stalls. The stalls are placed in a straight manner at positions from x1, x2, ...xN. But his cows (C) are aggressive and don’t want to be near other cows. To prevent cows from hurting each other, he wan…

Python---冒泡排序、选择排序

冒泡排序 依次输入n个数&#xff0c;进行冒泡排序 冒泡排序法&#xff0c;即两个相邻的进行比较&#xff0c;比较之后换位置 def bubbleSort(arr):n len(arr)for i in range(n):for j in range(0, n-i-1):if arr[j] > arr[j1] :arr[j], arr[j1] arr[j1], arr[j]arr[] n…

react js 添加样式_如何在React JS Application中添加图像?

react js 添加样式Hello! In this article, we will learn how to add images in React JS? I remember when I just started coding in React JS, I thought adding images would be done exactly as it is in HTML. I later realized that it was different. 你好&#xff0…

二叉树(多路平衡搜索树)-(代码、分析、汇编)

目录&#xff1a;代码&#xff1a;分析&#xff1a;汇编&#xff1a;代码&#xff1a; BTree.h #ifndef _BTREE_H_ #define _BTREE_H_#define BT_LEFT 0 //定义左子节点标识 #define BT_RIGHT 1 //定义右子节点标识typedef void BTree;//定义树类型 typedef unsigned long lo…

Python---二分法查找

输入n个数&#xff0c;通过二分法查找该数的下标 def binarySearch(arr,value):m 0#开始n len(arr#最后)while m<n:mid(mn)//2#计算中间位置if valuearr[mid]:#查找成功&#xff0c;返回元素对应的位置return midelif value>arr[mid]:#在后面一半元素中继续查找mmid1e…