打游戏要存进度-备忘录模式

打游戏要存进度-备忘录模式

学习自

《大话设计模式》

备忘录模式漫谈

备忘录的这种设计思想是非常常见的,比如说围棋游戏的悔棋,绘图软件的撤销功能等等,都或多或少的使用了备忘录模式来处理对象的状态。

备忘录(Memento): 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这种状态。这样以后就可以将该对象恢复到原来保存的状态。

我的理解
保存好重要数据以备反悔之时使用。

备忘录模式类图

图片.png | left | 787x166

  • 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;
    }
}

上面的代码向较于最初的版本多出了两个方法 SaveRoleStateRecoveryState 用来保存当前的角色状态和恢复角色的状态。

现在我们还差一个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

现在客户端已经无法观察到保存状态和恢复状态的细节了,所有的细节都被封装到了类中,现在如果对保存/恢复状态的业务进行修改,也不会影响到客户端的代码。

备忘录模式的弊端

如果备忘录模式需要存储的状态数据非常多的话,那么就会非常消耗内存。

posted @ 2018-07-11 22:08  鲁迅认识的那只猹  阅读(282)  评论(0编辑  收藏  举报