設(shè)計(jì)模式系列—備忘錄模式
模式定義
在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),以便以后當(dāng)需要時(shí)能將該對(duì)象恢復(fù)到原先保存的狀態(tài)。該模式又叫快照模式。
模版實(shí)現(xiàn)如下:
- package com.niuh.designpattern.memento.v1;
 - /**
 - * <p>
 - * 備忘錄模式
 - * </p>
 - */
 - public class MementoPattern {
 - public static void main(String[] args) {
 - Originator or = new Originator();
 - Caretaker cr = new Caretaker();
 - or.setState("S0");
 - System.out.println("初始狀態(tài):" + or.getState());
 - cr.setMemento(or.createMemento()); //保存狀態(tài)
 - or.setState("S1");
 - System.out.println("新的狀態(tài):" + or.getState());
 - or.restoreMemento(cr.getMemento()); //恢復(fù)狀態(tài)
 - System.out.println("恢復(fù)狀態(tài):" + or.getState());
 - }
 - }
 - //備忘錄
 - class Memento {
 - private String state;
 - public Memento(String state) {
 - this.state = state;
 - }
 - public void setState(String state) {
 - this.state = state;
 - }
 - public String getState() {
 - return state;
 - }
 - }
 - //發(fā)起人
 - class Originator {
 - private String state;
 - public void setState(String state) {
 - this.state = state;
 - }
 - public String getState() {
 - return state;
 - }
 - public Memento createMemento() {
 - return new Memento(state);
 - }
 - public void restoreMemento(Memento m) {
 - this.setState(m.getState());
 - }
 - }
 - //管理者
 - class Caretaker {
 - private Memento memento;
 - public void setMemento(Memento m) {
 - memento = m;
 - }
 - public Memento getMemento() {
 - return memento;
 - }
 - }
 
輸出結(jié)果如下:
- 初始狀態(tài):S0
 - 新的狀態(tài):S1
 - 恢復(fù)狀態(tài):S0
 
解決的問(wèn)題
備忘錄模式能記錄一個(gè)對(duì)象的內(nèi)部狀態(tài),當(dāng)用戶后悔時(shí)能撤銷當(dāng)前操作,使數(shù)據(jù)恢復(fù)到它原先的狀態(tài)。
每個(gè)人都有犯錯(cuò)誤的時(shí)候,都希望有種“后悔藥”能彌補(bǔ)自己的過(guò)失,讓自己重新開(kāi)始,但現(xiàn)實(shí)是殘酷的。在計(jì)算機(jī)應(yīng)用中,客戶同樣會(huì)常常犯錯(cuò)誤,能否提供“后悔藥”給他們呢?當(dāng)然是可以的,而且是有必要的。這個(gè)功能由“備忘錄模式”來(lái)實(shí)現(xiàn)。
模式組成
備忘錄模式的核心是設(shè)計(jì)備忘錄類以及用于管理備忘錄的管理者類。
實(shí)例說(shuō)明
實(shí)例概況
以游戲存檔為例,看一下如何用備忘錄模式實(shí)現(xiàn)
使用步驟
步驟1:定義備忘錄角色,用于存儲(chǔ)角色狀態(tài)。
- class RoleStateMemento {
 - private int vit; //生命力
 - private int atk; //攻擊力
 - private int def; //防御力
 - public RoleStateMemento(int vit, int atk, int def) {
 - this.vit = vit;
 - this.atk = atk;
 - this.def = def;
 - }
 - public int getVit() {
 - return vit;
 - }
 - public void setVit(int vit) {
 - this.vit = vit;
 - }
 - public int getAtk() {
 - return atk;
 - }
 - public void setAtk(int atk) {
 - this.atk = atk;
 - }
 - public int getDef() {
 - return def;
 - }
 - public void setDef(int def) {
 - this.def = def;
 - }
 - }
 
步驟2:定義發(fā)起人角色(當(dāng)前游戲角色),記錄當(dāng)前游戲角色的生命力、攻擊力、防御力。通過(guò)saveState()方法來(lái)保存當(dāng)前狀態(tài),通過(guò)recoveryState()方法來(lái)恢復(fù)角色狀態(tài)。
- class GameRole {
 - private int vit; //生命力
 - private int atk; //攻擊力
 - private int def; //防御力
 - public int getVit() {
 - return vit;
 - }
 - public void setVit(int vit) {
 - this.vit = vit;
 - }
 - public int getAtk() {
 - return atk;
 - }
 - public void setAtk(int atk) {
 - this.atk = atk;
 - }
 - public int getDef() {
 - return def;
 - }
 - public void setDef(int def) {
 - this.def = def;
 - }
 - //狀態(tài)顯示
 - public void stateDisplay() {
 - System.out.println("角色當(dāng)前狀態(tài):");
 - System.out.println("體力:" + this.vit);
 - System.out.println("攻擊力:" + this.atk);
 - System.out.println("防御力: " + this.def);
 - System.out.println("-----------------");
 - }
 - //獲得初始狀態(tài)
 - public void getInitState() {
 - this.vit = 100;
 - this.atk = 100;
 - this.def = 100;
 - }
 - //戰(zhàn)斗后
 - public void fight() {
 - this.vit = 0;
 - this.atk = 0;
 - this.def = 0;
 - }
 - //保存角色狀態(tài)
 - public RoleStateMemento saveState() {
 - return (new RoleStateMemento(vit, atk, def));
 - }
 - //恢復(fù)角色狀態(tài)
 - public void recoveryState(RoleStateMemento memento) {
 - this.vit = memento.getVit();
 - this.atk = memento.getAtk();
 - this.def = memento.getDef();
 - }
 - }
 
步驟3:定義管理者角色,角色狀態(tài)管理者
- class RoleStateCaretaker {
 - private RoleStateMemento memento;
 - public RoleStateMemento getMemento() {
 - return memento;
 - }
 - public void setMemento(RoleStateMemento memento) {
 - this.memento = memento;
 - }
 - }
 
步驟4:測(cè)試輸出
- public class MementoPattern {
 - // 邏輯大致為打boss前存檔,打boss失敗了
 - public static void main(String[] args) {
 - //打boss前
 - GameRole gameRole = new GameRole();
 - gameRole.getInitState();
 - gameRole.stateDisplay();
 - //保存進(jìn)度
 - RoleStateCaretaker caretaker = new RoleStateCaretaker();
 - caretaker.setMemento(gameRole.saveState());
 - //打boss失敗
 - gameRole.fight();
 - gameRole.stateDisplay();
 - //恢復(fù)狀態(tài)
 - gameRole.recoveryState(caretaker.getMemento());
 - gameRole.stateDisplay();
 - }
 - }
 
輸出結(jié)果
- 角色當(dāng)前狀態(tài):
 - 體力:100
 - 攻擊力:100
 - 防御力: 100
 - -----------------
 - 角色當(dāng)前狀態(tài):
 - 體力:0
 - 攻擊力:0
 - 防御力: 0
 - -----------------
 - 角色當(dāng)前狀態(tài):
 - 體力:100
 - 攻擊力:100
 - 防御力: 100
 
優(yōu)點(diǎn)
備忘錄模式是一種對(duì)象行為型模式,其主要優(yōu)點(diǎn)如下。
- 提供了一種可以恢復(fù)狀態(tài)的機(jī)制。當(dāng)用戶需要時(shí)能夠比較方便地將數(shù)據(jù)恢復(fù)到某個(gè)歷史的狀態(tài)。
 - 實(shí)現(xiàn)了內(nèi)部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外,其他對(duì)象都不能夠訪問(wèn)這些狀態(tài)信息。
 - 簡(jiǎn)化了發(fā)起人類。發(fā)起人不需要管理和保存其內(nèi)部狀態(tài)的各個(gè)備份,所有狀態(tài)信息都保存在備忘錄中,并由管理者進(jìn)行管理,這符合單一職責(zé)原則。
 
缺點(diǎn)
資源消耗大。如果要保存的內(nèi)部狀態(tài)信息過(guò)多或者特別頻繁,將會(huì)占用比較大的內(nèi)存資源。
注意事項(xiàng)
- 為了符合迪米特法則,需要有一個(gè)管理備忘錄的類
 - 不要在頻繁建立備份的場(chǎng)景中使用備忘錄模式。為了節(jié)約內(nèi)存,可使用原型模式+備忘錄模式
 
應(yīng)用場(chǎng)景
- 需要保存和恢復(fù)數(shù)據(jù)的相關(guān)場(chǎng)景
 - 提供一個(gè)可回滾的操作,如ctrl+z、瀏覽器回退按鈕、Backspace鍵等
 - 需要監(jiān)控的副本場(chǎng)景
 
模式的擴(kuò)展
在備忘錄模式中,有單狀態(tài)備份的例子,也有多狀態(tài)備份的例子??梢越Y(jié)合原型模式混合使用。在備忘錄模式中,通過(guò)定義“備忘錄”來(lái)備份“發(fā)起人”的信息,而原型模式的 clone() 方法具有自備份功能,所以,如果讓發(fā)起人實(shí)現(xiàn) Cloneable 接口就有備份自己的功能,這時(shí)可以刪除備忘錄類,其結(jié)構(gòu)如下:
源碼中的應(yīng)用
- #Spring
 - org.springframework.binding.message.StateManageableMessageContext
 
StateManageableMessageContext 部分源碼
- public interface StateManageableMessageContext extends MessageContext {
 - /**
 - * Create a serializable memento, or token representing a snapshot of the internal state of this message context.
 - * @return the messages memento
 - */
 - public Serializable createMessagesMemento();
 - /**
 - * Set the state of this context from the memento provided. After this call, the messages in this context will match
 - * what is encapsulated inside the memento. Any previous state will be overridden.
 - * @param messagesMemento the messages memento
 - */
 - public void restoreMessages(Serializable messagesMemento);
 - /**
 - * Configure the message source used to resolve messages added to this context. May be set at any time to change how
 - * coded messages are resolved.
 - * @param messageSource the message source
 - * @see MessageContext#addMessage(MessageResolver)
 - */
 - public void setMessageSource(MessageSource messageSource);
 - }
 
PS:以上代碼提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git




















 
 
 












 
 
 
 