打游戏要存进度-备忘录模式
学习自
《大话设计模式》
备忘录模式漫谈
备忘录的这种设计思想是非常常见的,比如说围棋游戏的悔棋,绘图软件的撤销功能等等,都或多或少的使用了备忘录模式来处理对象的状态。
备忘录(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
现在客户端已经无法观察到保存状态和恢复状态的细节了,所有的细节都被封装到了类中,现在如果对保存/恢复状态的业务进行修改,也不会影响到客户端的代码。
备忘录模式的弊端
如果备忘录模式需要存储的状态数据非常多的话,那么就会非常消耗内存。