[轉]深入淺出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.pswp.cn/news/379220.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379220.shtml
英文地址,請注明出處:http://en.pswp.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);…

Java LocalDate類| ofYearDay()方法與示例

LocalDate類的YearDay()方法 (LocalDate Class ofYearDay() method) ofYearDay() method is available in java.time package. ofYearDay()方法在java.time包中可用。 ofYearDay() method is used to create an instance of LocalDate object that holds the value from the ye…

ASP.NET C#讀寫Cookie的方法!

Cookie (HttpCookie的實例)提供了一種在 Web 應用程序中存儲用戶特定信息的方法。例如&#xff0c;當用戶訪問您的站點時&#xff0c;您可以使用 Cookie 存儲用戶首選項或其他信息。當該用戶再次訪問您的網站時&#xff0c;應用程序便可以檢索以前存儲的信息。 創建Cookie方法…

遞歸-裴波那契數列(代碼、分析、匯編)

目錄&#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;這里調用是一直調用左邊函…

javascript 事件委派

javascript 模擬用戶操作 <a href"javascript:;" onClick"javascript:alert(131231);" id"abc">asdfasdf</a> <script> if(document.all) { document.getElementById(abc).fireEvent(onclick); } else { var evt document.cr…

Java Duration類| isNegative()方法與示例

持續時間類isNegative()方法 (Duration Class isNegative() method) isNegative() method is available in java.time package. isNegative()方法在java.time包中可用。 isNegative() method is used to check whether this Duration object holds the value of length < 0 …

經典例題(一)

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…

asterisk撥號規則

一、前言 本文檔以asterisk-1.4.32為基礎寫作而成&#xff0c;可能和其他版本有些區別。其中參考了一些別的書籍和文章。因為寫的比較倉促&#xff0c;而且基本都是晚上寫的&#xff0c;里面的內容邏輯性和語句沒有仔細斟酌&#xff0c;就是想到什么寫什么&#xff0c;難免有…

getseconds補0_Java Duration類| getSeconds()方法與示例

getseconds補0持續時間類getSeconds()方法 (Duration Class getSeconds() method) getSeconds() method is available in java.time package. getSeconds()方法在java.time包中可用。 getSeconds() method is used to return the number of seconds exists in this Duration. g…

遞歸-漢諾塔(代碼、分析、匯編)

代碼&#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

kotlin字符串判空_Kotlin程序檢查空字符串,空字符串或NULL字符串

kotlin字符串判空Given a string, we have to check whether it is an empty, blank or NULL string. 給定一個字符串&#xff0c;我們必須檢查它是否為空&#xff0c;空白或NULL字符串。 Example: 例&#xff1a; Input:str ""Output:True用于在Kotlin中檢查Empt…

.net中對象序列化技術淺談 (轉)

原文&#xff1a;http://blog.csdn.net/zhoufoxcn/archive/2009/03/11/3978874.aspx .net中對象序列化技術淺談 (轉&#xff09;序列化是將對象狀態轉換為可保持或傳輸的格式的過程。與序列化相對的是反序列化&#xff0c;它將流轉換為對象。這兩個過程結合起來&#xff0c;可…

遞歸-輸出字符串所有的組合情況(代碼、分析、匯編)

目錄&#xff1a;代碼&#xff1a;分析&#xff1a;匯編&#xff1a;代碼&#xff1a; #include <stdio.h>/*程序描述&#xff1a;輸出字符串所有的組合情況使用permutation函數進行將指定的下標值&#xff0c;與最大下標值這個范圍的每個下標值進行交換每調用一次permu…

課本例子代碼第四章

【例4.1】設計一個控制臺應用程序&#xff0c;采用二分查找方法在給定的有序數組a中查找用戶輸入的值&#xff0c;并提示相應的查找結果。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace C…

一個簡單的python日志服務器

一個簡單的python日志服務器&#xff0c;主要目的是搜集各python logging記錄的日志&#xff0c;將其簡單匯總。源碼如下&#xff1a; # -*- coding: utf-8 -*-Created on 2012-06-14 19:50 summary: a simple logging server. use gevent and logging modules author: JerryK…

c#中queue_C#中的Queue.Enqueue()方法示例

c#中queueC&#xff03;Queue.Enqueue()方法 (C# Queue.Enqueue() method) Queue.Enqueue() method is used to add an object/element at the end of the Queue. Queue.Enqueue()方法用于在Queue的末尾添加一個對象/元素。 Syntax: 句法&#xff1a; void Queue.Enqueue(Obj…

C#調用Web Service時的身份驗證

轉自&#xff1a;http://www.anqn.com/dev/vc/2010-01-23/a09122769.shtml 在項目開發&#xff0c;我們經常會使用WebService&#xff0c;但在使用WebService時我們經常會考慮以下問題&#xff1a;怎么防止別人訪問我的WebService?從哪里引用我的WebService?對于第一個問題&a…

遞歸-計算字符串長度(代碼、分析、匯編)

目錄&#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…