設(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í)能撤銷(xiāo)當(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