設計模式_備忘錄模式

Mememento Pattern 
    Without violating encapsulation,capture and externalize an object's internal state so that the object can be restored to this state later.(在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣就可將該對象恢復到原先保存的狀態。)
又稱Tonken模式

不認識的單詞
violating  違反,褻瀆
externalize  給…以外形,使客觀化,使具體化 
internal  國內的;內部的;體內的;內心的 

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

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

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


帶目的去看問題往往會很清晰。

public class Originator {
 private String state="";
 
 public void changeState(){
  this.state="maybe not so good";
 }
 public String getState() {
  return state;
 }
 public void setState(String state) {
  this.state = state;
 }
 
 public Memento createMemote(){
  return new Memento(this.state);
 }
 
 public void restoreMomoto(Memento _memoto){
  this.setState(_memoto.getState());
 }
}

public class Memento {
 private String state="";
 
 public Memento(String _state) {
  this.state=_state;
 }
 public String getState() {
  return this.state;
 }
 
}

public class Caretaker {
 private Memento memoto;
 
 public Memento getMemoto(){
  return memoto;
 }
 
 public void setMemoto(Memento memoto){
  this.memoto=memoto;
 }
}

public class Client {
 
 public static void main(String[] args) {
  Originator orihinator=new Originator();	 //發起人
  Caretaker caretaker=new Caretaker();  	 //備忘錄管理者,不能改變內容
  orihinator.setState("It's so good");	 //設置狀態
  caretaker.setMemoto(orihinator.createMemote());  //創建備份點
  orihinator.changeState();                        //改變狀態
  orihinator.restoreMomoto(caretaker.getMemoto());  //恢復至備份點
 }
}
/*
 *  這樣看來,Memoto備忘錄就是一個JavaBean,存儲數據。
 *  爲什麼還需要Caretaker? 爲了不違背迪米特原則,只和朋友類交流,備份類不是朋友,系統需求只是在某個點創建備份
 *  所以備忘錄管理類就出來了,看起來又是一個JavaBean,在備忘錄之上又包裝了一層,麻煩,但是這樣結構清晰了許多
 *  發起人不需要和備份類交流
 *  
 *  例子 :  網站   -> 瀏覽器 -> cookie -> 網站...  
 *  用戶發送一個請求給服務器,服務器會創建一個相對應的session對象,發送數據至瀏覽器並設置cookie,在一段時間裏
 *  用戶可以訪問服務器,服務器可以根據sessionID來判斷是哪個session對象
 */

多狀態備份 
在發起人中成員屬性多個狀態,在備忘錄類就是一個hashMap,存放多個狀態,通過BeanUtils工具類注入參數和返回參數。 

public class BeanUtils {
//把bean的所有屬性及數值放入到Hashmap中
public static HashMap<String,Object> backupProp(Object bean){
HashMap<String,Object> result = new HashMap<String,Object>();
try {
//獲得Bean描述
BeanInfo beanInfo=Introspector.getBeanInfo(bean.getClass());
//獲得屬性描述
PropertyDescriptor[] descriptors=beanInfo.getPropertyDescriptors();
//遍歷所有屬性
for(PropertyDescriptor des:descriptors){
//屬性名稱
String fieldName = des.getName();
//讀取屬性的方法
Method getter = des.getReadMethod();
//讀取屬性值
Object fieldValue=getter.invoke(bean,new Object[]{});
if(!fieldName.equalsIgnoreCase("class")){
result.put(fieldName, fieldValue);
}
}
} catch (Exception e) {
//異常處理
}
return result;
}
//把HashMap的值返回到bean中
public static void restoreProp(Object bean,HashMap<String,Object> propMap){
try {
//獲得Bean描述
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
//獲得屬性描述
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
//遍歷所有屬性
for(PropertyDescriptor des:descriptors){
//屬性名稱
String fieldName = des.getName();
//如果有這個屬性
if(propMap.containsKey(fieldName)){
//寫屬性的方法
Method setter = des.getWriteMethod();
setter.invoke(bean, new Object[]{propMap.get(fieldName)});
}
}
} catch (Exception e) {
//異常處理
System.out.println("shit");
e.printStackTrace();
}
}
}

多備份備忘錄 
    檢查點,在caretaker中稍微做點修改

public class Caretaker {
//容納備忘錄的容器
private HashMap<String,Memento> memMap = new HashMap<String,Memento>();
public Memento getMemento(String idx) {
return memMap.get(idx);
}
public void setMemento(String idx,Memento memento) {
this.memMap.put(idx, memento);
}
}


權限設計
    只能由發起人訪問,私有的內置類,但是備忘錄管理需要關聯,這個時候就可以使用空接口,但是訪問屬性可以通過反射和修改數據,這是一種設計方法,叫做"雙接口設計",將Memento類內置於Originator ,這樣只能由Originator 訪問了。

雙接口設計
雙接口設計,我們的一個類可以實現多個接口, 在系統設計時,如果考慮對象的安全問題,則可以提供兩個接口,一個是業務的正常接口, 實現必要的業務邏輯,叫做寬接口;另外一個接口是一個空接口,什麼方法都沒有,其目的 是提供給子系統外的模塊訪問,比如容器對象,這個叫做窄接口,由於窄接口中沒有提供任 何操縱數據的方法,因此相對來說比較安全。

因爲一個一個寫例子的確看起來很簡單,我修改了大約40分鐘把三者功能合一,其中有些地方確實該的不好,但是至少明白了其中的道。

public class Originator { 
 private String state1="";  //狀態1
 private String state2="";  //狀態2
 private String state3="";  //狀態3
 
 public String getState1() {
  return state1;
 }
 public void setState1(String state1) {
  this.state1 = state1;
 }
 public String getState2() {
  return state2;
 }
 public void setState2(String state2) {
  this.state2 = state2;
 }
 public String getState3() {
  return state3;
 }
 public void setState3(String state3) {
  this.state3 = state3;
 }
 /**
  * 創建備份,將該實例的所有屬性通過反射得到HashMap集合
  * 然後通過Memento的構造方法注入startMap中
  * @return
  */
 public Memento createMemote(){
  return new Memento(BeanUtils.backupProp(this));
 }
 
 /**
  * 因爲權限的關係,外部不可能傳進來一個Memento對象,這裏就將就的使用了一個HashMap集合
  * 通過反射,將該實例的所有屬性值替換成hashMap中的值
  * @param map
  */
 public void restoreMomoto(HashMap<String,Object> map){
  BeanUtils.restoreProp(this, map);
 }
 
 /**
  * 內置類,別人不可能訪問
  * 實現一個空接口是爲了與外部關聯,在實際的開發中一個業務接口,一個空接口用於其它子模塊,通過反射就可以爲所欲爲了
  * @author suibian
  */
 private class Memento implements IMemento{
  private HashMap<String,Object> startMap;
   
  public Memento(HashMap<String,Object> _startMap) {
   this.startMap=_startMap;
  }
  public HashMap<String, Object> getStartMap() {
   return startMap;
  }
  public void setStartMap(HashMap<String, Object> startMap) {
   this.startMap = startMap;
  }
 }
 /*測試使用
  */
 @Override
 public String toString() {
  return "state1="+state1+"\t state2="+state2+"\t state3="+state3;
 }
}

public class Caretaker {
 
 private HashMap<String,IMemento> map=new HashMap<String,IMemento>();
 
 public IMemento getMemoto(String idx){
  return map.get(idx);
 }
 /**
  * 多個備份
  * @param idx  時間銼
  * @param memoto 備份接口
  */
 public void setMemoto(String idx,IMemento memoto){
  this.map.put(idx, memoto);
 }
}

public class Client {
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
  Originator ori1 = new Originator();
  Caretaker caretaker = new Caretaker();
  ori1.setState1("備份1狀態1");
  ori1.setState2("備份1狀態2");
  ori1.setState3("備份1狀態3");
  caretaker.setMemoto("第一個", ori1.createMemote());
  Originator ori2 = new Originator();
  ori2.setState1("備份2狀態1");
  ori2.setState2("備份2狀態2");
  ori2.setState3("備份2狀態3");
  caretaker.setMemoto("第二個", ori2.createMemote());
  System.out.println(ori1.toString());
  System.out.println(ori2.toString());
  System.out.println("----------------------備份完畢----------------------------");
 
  ori1.setState1("備份1狀態1改變了");
  System.out.println("改變後的:"+ori1.toString());
 
  System.out.println("-----------------------開始恢復-----------------------------");
 
  IMemento memento = caretaker.getMemoto("第一個");  //從備份管理獲取備份實例
  //得到是Memento中的屬性startMap再轉化成能注參的map對象
  HashMap<String,Object> data=(HashMap<String, Object>) BeanUtils.backupProp(memento).get("startMap");
 
  for(Entry<String, Object> e:data.entrySet()){
   System.out.println("這是存起來的備份數據:"+e.getKey()+"\t"+e.getValue());
  }
  //將備份實例中的數據data通過反射注入到ori1中去
  ori1.restoreMomoto(data);
  System.out.println("------------------恢復完後的數據-------------------------");
  System.out.println(ori1.toString());
  System.out.println(ori2.toString());
 }
}
/*錯誤地方
 *通過 BeanUtils.backupProp(memento)獲取的是對象Memento類的所有屬性值
 *而Originator恢復需要的是一個hashMap通過反射注參,一個屬性名對應一個值,而上面獲取的hashMap是一個屬性名,一個hashMap對象
 *因爲權限只能由Originator來恢復,所以修改了restoreMomoto()方法,不傳Memento,而傳一個HashMap對象
 */

不得不說,這個真麻煩。 


我是菜鳥,我在路上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章