設計模式--備忘錄模式Memento(行為型)

benbenxiongyuan發表於2014-04-15

定義:

1.1 定義:Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later. (在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可將該物件恢復到原先儲存的狀態。)

1.2 通用類圖:

個人認為更易理解的類圖

Originator發起人角色

記錄當前時刻的內部狀態,負責定義哪些屬於備份範圍的狀態,負責建立和恢復備忘錄資料。

Memento備忘錄角色

負責儲存Originator發起人物件的內部狀態,在需要的時候提供發起人需要的內部狀態。

Caretaker備忘錄管理員角色

對備忘錄進行管理、儲存和提供備忘錄。

1.3 通用程式碼:

 
public class Originator {
	// 內部狀態
	private String state = "";

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	// 建立一個備忘錄
	public Memento createMemento() {
		return new Memento(this.state);
	}

	// 恢復一個備忘錄
	public void restoreMemento(Memento _memento) {
		this.setState(_memento.getState());
	}
}

public class Memento {
	// 發起人的內部狀態
	private String state = "";

	// 建構函式傳遞引數
	public Memento(String _state) {
		this.state = _state;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
}

public class Caretaker {
	// 備忘錄物件
	private Memento memento;

	public Memento getMemento() {
		return memento;
	}

	public void setMemento(Memento memento) {
		this.memento = memento;
	}
}

public class Client {
	public static void main(String[] args) {
		// 定義出發起人
		Originator originator = new Originator();
		// 定義出備忘錄管理員
		Caretaker caretaker = new Caretaker();
		// 建立一個備忘錄
		caretaker.setMemento(originator.createMemento());
		// 恢復一個備忘錄
		originator.restoreMemento(caretaker.getMemento());
	}
}


優點

暫無

缺點

暫無

應用場景

4.1 需要保有存和恢復資料的相關狀態場景;

4.2 提供一個可回滾的操作;

4.3 需要監控的副本場景中;(如要監控一個物件的屬性,但監控又不應該作為系統的主業務來呼叫,它只是邊緣應用,即使出現監控不準、錯誤報警也影響不大。)

4.4 資料庫中的事務處理就使用備忘錄模式。

注意事項

5.1 備忘錄的生命週期:建立就要使用,要主動管理它的生命。

5.2 備忘錄的效能:不要在頻繁建立備份的場景中使用備忘錄模式(比如一個for迴圈中),原因是:一是控制不了其數量,二是大物件的建立是要消耗資源的,系統的效能需要考慮。

擴充套件

6.1 clone方式的備忘錄:通過複製的方式產生一個物件的內部狀態,類圖如下:

從類圖上來看,發起人角色融合了發起人角色和備忘錄角色,程式碼如下:

public class Originator implements Cloneable {
	// 內部狀態
	private String state = "";

	// 此二方法原為Memento所有。
	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	// 建立一個備忘錄
	public Originator createMemento() {
		return this.clone();
	}

	// 恢復一個備忘錄
	public void restoreMemento(Originator _originator) {
		this.setState(_originator.getState());
	}

	// 克隆當前物件
	@Override
	protected Originator clone() {
		try {
			return (Originator) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return null;
	}
}

public class Caretaker {
	// 發起人物件
	private Originator originator;

	public Originator getOriginator() {
		return originator;
	}

	public void setOriginator(Originator originator) {
		this.originator = originator;
	}
}

public class Client {
	public static void main(String args[]) {
		Originator o = new Originator();
		Caretaker c = new Caretaker();
		o.setState("version 1.0.0");
		c.setOriginator(o.clone());
		System.out.println("當前狀態:" + o.getState());
		o.setState("version 2.0.0");
		System.out.println("修改後狀態:" + o.getState());
		o.restoreMemento(c.getOriginator());
		System.out.println("恢復後狀態:" + o.getState());
	}
}

測試:

當前狀態:version 1.0.0

修改後狀態:version 2.0.0

恢復後狀態:version 1.0.0

仍可以進一步精簡,去掉管理備忘錄角色

public class Originator implements Cloneable {
	private Originator backup;
	// 內部狀態
	private String state = "";

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	// 建立一個備忘錄
	public void createMemento() {
		this.backup = this.clone();
	}

	// 恢復一個備忘錄
	public void restoreMemento() {
		// 在進行恢復前應該進行斷言,防止空指標
		this.setState(this.backup.getState());
	}

	// 克隆當前物件
	@Override
	protected Originator clone() {
		try {
			return (Originator) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return null;
	}
}

public class Client {
	public static void main(String[] args) {
		// 定義發起人
		Originator originator = new Originator();
		// 建立初始狀態
		originator.setState("初始狀態...");
		System.out.println("初始狀態是:" + originator.getState());
		// 建立備份
		originator.createMemento();
		// 修改狀態
		originator.setState("修改後的狀態...");
		System.out.println("修改後狀態是:" + originator.getState());
		// 恢復原有狀態
		originator.restoreMemento();
		System.out.println("恢復後狀態是:" + originator.getState());
	}
}

對比以上兩例,可以發現:程式精簡了很多,而且高層模組的依賴也減少了。現在再考慮一下原型模式深拷貝與淺拷貝的問題,在複雜的場景下它會讓你的程式邏輯異常混亂,出現錯誤也很難跟蹤。因此Clone方式的備忘錄模式適用於較簡單的場景

6.2 多狀態的備忘錄模式:實際開發中,一個物件可能有多個狀態,一個JavaBean有多個屬性非常常見,如果隨上所述,就要寫一堆的狀態備份、還原語句?這裡介紹一個工具類BeanUtils,可以把類的所有屬性值轉換到HashMap中,亦可以把HashMap中的值放入物件中。

此方案的類圖如下:

原始碼如下:

public class Originator {
	// 內部狀態
	private String state1 = "";
	private String state2 = "";
	private String state3 = "";

	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;
	}

	// 建立一個備忘錄
	public Memento createMemento() {
		return new Memento(BeanUtils.backupProp(this));
	}

	// 恢復一個備忘錄
	public void restoreMemento(Memento _memento) {
		BeanUtils.restoreProp(this, _memento.getStateMap());
	}

	// 增加一個toString方法
	@Override
	public String toString() {
		return "state1=" + state1 + "\nstat2=" + state2 + "\nstate3=" + state3;
	}
}

public class Memento {
	// 接受HashMap作為狀態
	private HashMap<String, Object> stateMap;

	// 接受一個物件,建立一個備份
	public Memento(HashMap<String, Object> map) {
		this.stateMap = map;
	}

	public HashMap<String, Object> getStateMap() {
		return stateMap;
	}

	public void setStateMap(HashMap<String, Object> stateMap) {
		this.stateMap = stateMap;
	}
}

public class Caretaker {
	// 備忘錄物件
	private Memento memento;

	public Memento getMemento() {
		return memento;
	}

	public void setMemento(Memento memento) {
		this.memento = memento;
	}
}

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();
		}
	}
}

public class Client {
	public static void main(String[] args) {
		// 定義出發起人
		Originator ori = new Originator();
		// 定義出備忘錄管理員
		Caretaker caretaker = new Caretaker();
		// 初始化
		ori.setState1("中國");
		ori.setState2("強盛");
		ori.setState3("繁榮");
		System.out.println("===初始化狀態===\n" + ori);
		// 建立一個備忘錄
		caretaker.setMemento(ori.createMemento());
		// 修改狀態值
		ori.setState1("軟體");
		ori.setState2("架構");
		ori.setState3("優秀");
		System.out.println("\n===修改後狀態===\n" + ori);
		// 恢復一個備忘錄
		ori.restoreMemento(caretaker.getMemento());
		System.out.println("\n===恢復後狀態===\n" + ori);
	}
}

通過這種方式,無論有多少狀態都可以。

6.3 多備份的備忘錄:可在通用原始碼上新增如下即可:

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);
	}
}

public class Client {
	public static void main(String[] args) {
		// 定義出發起人
		Originator originator = new Originator();
		// 定義出備忘錄管理員
		Caretaker caretaker = new Caretaker();
		// 建立兩個備忘錄
		caretaker.setMemento("001", originator.createMemento());
		caretaker.setMemento("002", originator.createMemento());
		// 恢復一個指定標記的備忘錄
		originator.restoreMemento(caretaker.getMemento("001"));
	}
}

注意:要嚴格限定備忘錄的建立,建議增加MAP的上限,否則系統很容易產生記憶體溢位情況。

6.4 更好的封裝:雙介面(一個類可以實現多個介面,在系統設計時,如果考慮物件的安全問題,可以提供兩個介面,一個是業務的正常介面,實現必要的業務邏輯,叫做寬介面;另外一個介面是一個空介面,什麼方法都沒有,其目的是提供給子系統外的模組訪問,因此較安全。)

類圖如下:

原始碼如下:

public class Originator {
	// 內部狀態
	private String state = "";

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	// 建立一個備忘錄
	public IMemento createMemento() {
		return new Memento(this.state);
	}

	// 恢復一個備忘錄
	public void restoreMemento(IMemento _memento) {
		this.setState(((Memento) _memento).getState());
	}

	// 內建類
	private class Memento implements IMemento {
		// 發起人的內部狀態
		private String state = "";

		// 建構函式傳遞引數
		private Memento(String _state) {
			this.state = _state;
		}

		private String getState() {
			return state;
		}

		private void setState(String state) {
			this.state = state;
		}
	}
}

public interface IMemento {
}

public class Caretaker {
	// 備忘錄物件
	private IMemento memento;

	public IMemento getMemento() {
		return memento;
	}

	public void setMemento(IMemento memento) {
		this.memento = memento;
	}
}

public class Client {
	public static void main(String[] args) {
		// 定義出發起人
		Originator originator = new Originator();
		// 定義出備忘錄管理員
		Caretaker caretaker = new Caretaker();
		// 建立一個備忘錄
		caretaker.setMemento(originator.createMemento());
		// 恢復一個備忘錄
		originator.restoreMemento(caretaker.getMemento());
	}
}


內建類Memento全部是private訪問許可權,也就是說除了發起人外,別人休想訪問到,如果要產生關聯關係,就通過空介面進行。

轉自:http://blog.csdn.net/yuanlong_zheng/article/details/7584848

相關文章