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