打游戲要存進度-備忘錄模式
學習自
《大話設計模式》
備忘錄模式漫談
備忘錄的這種設計思想是非常常見的,比如說圍棋游戲的悔棋,繪圖軟件的撤銷功能等等,都或多或少的使用了備忘錄模式來處理對象的狀態。
備忘錄(Memento): 在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這種狀態。這樣以后就可以將該對象恢復到原來保存的狀態。
我的理解
保存好重要數據以備反悔之時使用。
備忘錄模式類圖
- Originator:是備忘錄的創建者
- Memento: 是備忘錄對象
- Caretaker: 持有備忘錄對象
沒有使用備忘錄模式的代碼
下面這一段代碼是模擬了一下,在玩游戲的時候對角色狀態的存檔與恢復。
public class GameRole
{public int Vitality { get; set; }public int Attack { get; set; }public int Defense { get; set; }public void StateDisplay(){Console.WriteLine("角色當前狀態");Console.WriteLine("體力:{0}", this.Vitality);Console.WriteLine("攻擊力:{0}", this.Attack);Console.WriteLine("防御力:{0}", this.Defense);Console.WriteLine();}public void GetInitState(){this.Vitality = 100;this.Attack = 100;this.Defense = 100;}public void Fight(){this.Vitality = 0;this.Attack = 0;this.Defense = 0;}
}static void Main(string[] args)
{GameRole gr = new GameRole();gr.GetInitState();gr.StateDisplay();//保存進度//!! 這里暴露了細節GameRole grBackup = new GameRole();grBackup.Vitality = gr.Vitality;grBackup.Attack = gr.Attack;grBackup.Defense = gr.Defense;gr.Fight();gr.StateDisplay();//回復之前的狀態//!!這里暴露的細節gr.Vitality = grBackup.Vitality;gr.Attack = grBackup.Attack;gr.Defense = grBackup.Defense;gr.StateDisplay();Console.ReadKey();
}//輸出結果
角色當前狀態
體力:100
攻擊力:100
防御力:100角色當前狀態
體力:0
攻擊力:0
防御力:0角色當前狀態
體力:100
攻擊力:100
防御力:100
上面的代碼將所有的細節暴露給了客戶端,導致客戶端承擔了太多的職責(保存狀態,恢復狀態,進行游戲),而且如果一旦游戲人物的屬性修改或者添加了,那么客戶端相關的代碼也必須修改,這些代碼緊緊地耦合在了一起。
使用了備忘錄模式的代碼
首先游戲角色這個類并不一定所有的屬性都需要備份/存檔,我們只需要把我們關系的數據進行存檔即可,為了存檔這些數據我們需要封裝起來,實現職責的分離。
public class RoleStateMemento
{public int Vitality { get; set; }public int Attack { get; set; }public int Defense { get; set; }public RoleStateMemento(int vitality, int attack, int defense){this.Vitality = vitality;this.Attack = attack;this.Defense = defense;}
}
有了存儲狀態的 Memento
對象后,我們再來修改一下 GameRole
這個類
public class GameRole
{public int Vitality { get; set; }public int Attack { get; set; }public int Defense { get; set; }public void StateDisplay(){Console.WriteLine("角色當前狀態");Console.WriteLine("體力:{0}", this.Vitality);Console.WriteLine("攻擊力:{0}", this.Attack);Console.WriteLine("防御力:{0}", this.Defense);Console.WriteLine();}public void GetInitState(){this.Vitality = 100;this.Attack = 100;this.Defense = 100;}public void Fight(){this.Vitality = 0;this.Attack = 0;this.Defense = 0;}/// <summary>/// 存檔狀態/// </summary>/// <returns></returns>public RoleStateMemento SaveRoleState(){return new RoleStateMemento(this.Vitality, this.Attack, this.Defense);}/// <summary>/// 恢復狀態/// </summary>/// <param name="memento"></param>public void RecoveryState(RoleStateMemento memento){this.Vitality = memento.Vitality;this.Attack = memento.Attack;this.Defense = memento.Defense;}
}
上面的代碼向較于最初的版本多出了兩個方法 SaveRoleState
和 RecoveryState
用來保存當前的角色狀態和恢復角色的狀態。
現在我們還差一個Memento的持有者
public class RoleStateCaretaker
{public RoleStateMemento RoleStateMemento { get; set; }
}
接下來我們看看客戶端的調用
static void Main(string[] args)
{GameRole gr = new GameRole();gr.GetInitState();gr.StateDisplay();//存檔RoleStateCaretaker caretaker = new RoleStateCaretaker();caretaker.RoleStateMemento = gr.SaveRoleState();//進行游戲gr.Fight();gr.StateDisplay();//恢復狀態 gr.RecoveryState(caretaker.RoleStateMemento);gr.StateDisplay();Console.ReadKey();
}
//輸出結果
角色當前狀態
體力:100
攻擊力:100
防御力:100角色當前狀態
體力:0
攻擊力:0
防御力:0角色當前狀態
體力:100
攻擊力:100
防御力:100
現在客戶端已經無法觀察到保存狀態和恢復狀態的細節了,所有的細節都被封裝到了類中,現在如果對保存/恢復狀態的業務進行修改,也不會影響到客戶端的代碼。
備忘錄模式的弊端
如果備忘錄模式需要存儲的狀態數據非常多的話,那么就會非常消耗內存。