熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

深入淺出Java設計之備忘錄模式

2013-11-23 19:42:01  來源: Java高級技術 

  引子

  俗話說世上難買後悔藥所以凡事講究個三思而後行但總常見有人做痛心疾首當初我要是……如果真的有《大話西游》中能時光倒流的月光寶盒那這世上也許會少一些傷感與後悔——當然這只能是癡人說夢了

  但是在我們手指下的程序世界裡卻有的後悔藥買今天我們要講的備忘錄模式便是程序世界裡的月光寶盒

定義與結構

  備忘錄(Memento)模式又稱標記(Token)模式GOF給備忘錄模式的定義為在不破壞封裝性的前提下捕獲一個對象的內部狀態並在該對象之外保存這個狀態這樣以後就可將該對象恢復到原先保存的狀態

  在講命令模式的時候我們曾經提到利用中間的命令角色可以實現undoredo的功能從定義可以看出備忘錄模式是專門來存放對象歷史狀態的這對於很好的實現undoredo功能有很大的幫助所以在命令模式中undoredo功能可以配合備忘錄模式來實現

  其實單就實現保存一個對象在某一時刻的狀態的功能還是很簡單的——將對象中要保存的屬性放到一個專門管理備份的對象中需要的時候則調用約定好的方法將備份的屬性放回到原來的對象中去但是你要好好看看為了能讓你的備份對象訪問到原對象中的屬性是否意味著你就要全部公開或者包內公開對象原本私有的屬性呢?如果你的做法已經破壞了封裝那麼就要考慮重構一下了

  備忘錄模式只是GOF對恢復對象某時的原有狀態這一問題提出的通用方案因此在如何保持封裝性上——由於受到語言特性等因素的影響備忘錄模式並沒有詳細描述只是基於C++闡述了思路那麼基於Java的應用應該怎樣來保持封裝呢?我們將在實現一節裡面討論

  來看下月光寶盒備忘錄模式的組成部分

  ) 備忘錄(Memento)角色備忘錄角色存儲備忘發起角色的內部狀態備忘發起角色根據需要決定備忘錄角色存儲備忘發起角色的哪些內部狀態為了防止備忘發起角色以外的其他對象訪問備忘錄備忘錄實際上有兩個接口備忘錄管理者角色只能看到備忘錄提供的窄接口——對於備忘錄角色中存放的屬性是不可見的備忘發起角色則能夠看到一個寬接口——能夠得到自己放入備忘錄角色中屬性

  ) 備忘發起(Originator)角色備忘發起角色創建一個備忘錄用以記錄當前時刻它的內部狀態在需要時使用備忘錄恢復內部狀態

  ) 備忘錄管理者(Caretaker)角色負責保存好備忘錄不能對備忘錄的內容進行操作或檢查

  舉例

  按照定義中的要求備忘錄角色要保持完整的封裝最好的情況便是備忘錄角色只應該暴露操作內部存儲屬性的的接口給備忘發起角色而對於其他角色則是不可見的GOF在書中以C++為例進行了探討但是在Java中沒有提供類似於C++中友元的概念在Java中怎樣才能保持備忘錄角色的封裝呢?

  下面對三種在Java中可保存封裝的方法進行探討

  第一種就是采用兩個不同的接口類來限制訪問權限這兩個接口類中一個提供比較完備的操作狀態的方法我們稱它為寬接口而另一個則可以只是一個標示我們稱它為窄接口備忘錄角色要實現這兩個接口類這樣對於備忘發起角色采用寬接口進行訪問而對於其他的角色或者對象則采用窄接口進行訪問

  這種實現比較簡單但是需要人為的進行規范約束——而這往往是沒有力度的

  第二種方法便很好的解決了第一種的缺陷采用內部類來控制訪問權限將備忘錄角色作為備忘發起角色的一個私有內部類好處我不詳細解釋了看看代碼吧就明白了下面的代碼是一個完整的備忘錄模式的教學程序它便采用了第二種方法來實現備忘錄模式

  還有一點值得指出的是在下面的代碼中對於客戶程序來說備忘錄管理者角色是不可見的這樣簡化了客戶程序使用備忘錄模式的難度下面采用備忘發起角色來調用訪問備忘錄管理者角色也可以參考門面模式在客戶程序與備忘錄角色之間添加一個門面角色

 class Originator{

  //這個是要保存的狀態
  private int state= ;
  //保持一個備忘錄管理者角色的對象
  private Caretaker c = new Caretaker();
  //讀取備忘錄角色以恢復以前的狀態
  public void setMemento(){
   Memento memento = (Memento)cgetMemento();
   state = mementogetState();
   Systemoutprintln(the state is +state+ now);
  }
  //創建一個備忘錄角色並將當前狀態屬性存入托給備忘錄管理者角色存放

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

  public void modifyStateTest(int m){
   state = m;
   Systemoutprintln(the state is +state+ now);
  }

  //作為私有內部類的備忘錄角色它實現了窄接口可以看到在第二種方法中寬接口已經不再需要
  //注意裡面的屬性和方法都是私有的

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

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

 //測試代碼——客戶程序

 public class TestInnerClass{
  public static void main(String[] args){
   Originator o = new Originator();
   ocreateMemento();
   omodifyStateTest();
   osetMemento();
  }
 }

 //窄接口

 interface MementoIF{}

 //備忘錄管理者角色

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


  第三種方式是不太推薦使用的使用clone方法來簡化備忘錄模式由於Java提供了clone機制這使得復制一個對象變得輕松起來使用了clone機制的備忘錄模式備忘錄角色基本可以省略了而且可以很好的保持對象的封裝但是在為你的類實現clone方法時要慎重啊

  在上面的教學代碼中我們簡單的模擬了備忘錄模式的整個流程在實際應用中我們往往需要保存大量備忘發起角色的歷史狀態這時就要對我們的備忘錄管理者角色進行改造最簡單的方式就是采用容器來按照順序存放備忘錄角色這樣就可以很好的實現undoredo功能了

適用情況

  從上面的討論可以看出使用了備忘錄模式來實現保存對象的歷史狀態可以有效地保持封裝邊界使用備忘錄可以避免暴露一些只應由備忘發起角色管理卻又必須存儲在備忘發起角色之外的信息備忘發起角色內部信息對其他對象屏蔽起來 從而保持了封裝邊界

  但是如果備份的備忘發起角色存在大量的信息或者創建恢復操作非常頻繁則可能造成很大的開銷

  GOF在《設計模式》中總結了使用備忘錄模式的前提

  ) 必須保存一個對象在某一個時刻的(部分)狀態 這樣以後需要時它才能恢復到先前的狀態

  ) 如果一個用接口來讓其它對象直接得到這些狀態將會暴露對象的實現細節並破壞對象的封裝性

總結

  介紹了怎樣來使用備忘錄模式實現存儲對象歷史狀態的功能並對基於Java的實現進行了討論歡迎大家指正


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27324.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.