软件编程
位置:首页>> 软件编程>> C#编程>> C#备忘录人生存档的设计模式实例

C#备忘录人生存档的设计模式实例

作者:老胡写代码  发布时间:2022-06-15 12:40:38 

标签:C#,备忘录,设计模式

C#备忘录设计模式

大家好,老胡又和大家见面了。首先承认今天的博客有点标题党了,人生是没有存档,也没有后悔药的。有存档和后悔药的,那是游戏,不知道这是不是游戏让人格外放松的原因之一。

今天恰逢端午放假,就让我们来试着做一个小游戏吧,顺带看看备忘录模式是如何在这种情况下面工作的。

游戏背景

这是一个简单的打怪游戏,有玩家,有怪兽,玩家作为主角光环,有如下三个特殊能力

  • 攻击怪兽有暴击几率

  • 有几率回避怪兽攻击

  • 可以自己治疗一定生命值

游戏实现

角色类

角色基类

首先是角色类,角色类提供玩家和怪兽最基本的抽象,比如血量、攻击力、攻击和治疗。(对于怪兽来说,治疗是没有提供实现的,坏人肯定不能再治疗了)

class Character
{
   public int HealthPoint { get; set; }
   public int AttackPoint { get; set; }        
   public virtual void AttackChracter(Character opponent)
   {
       opponent.HealthPoint -= this.AttackPoint;
       if (opponent.HealthPoint < 0)
       {
           opponent.HealthPoint = 0;
       }
   }
   public virtual void Cure()
   {
//故意留空给子类实现
   }
}

玩家类

玩家实现了治疗功能并且有暴击几率。

class Player : Character
{
   private float playerCriticalPossible;
   public Player(float critical)
   {
       playerCriticalPossible = critical;
   }
   public override void AttackChracter(Character opponent)
   {
       base.AttackChracter(opponent);
       Console.WriteLine("Player Attacked Monster");
       Random r = new Random();
       bool critical = r.Next(0, 100) < playerCriticalPossible * 100;
       if (critical)
       {
           base.AttackChracter(opponent);
           Console.WriteLine("Player Attacked Monster again");
       }
   }
   public override void Cure()
   {
       Random r = new Random();
       HealthPoint += r.Next(5, 10);
       Console.WriteLine("Player cured himself");
   }
}

怪兽类

怪兽没有治疗能力但是有一定的几率丢失攻击目标。

class Monster : Character
{
   private float monsterMissingPossible;
   public Monster(float missing)
   {
       monsterMissingPossible = missing;
   }
   public override void AttackChracter(Character opponent)
   {
       Random r = new Random();
       bool missing = r.Next(0, 100) < monsterMissingPossible * 100;
       if (missing)
       {
           Console.WriteLine("Monster missed it");
       }
       else
       {
           base.AttackChracter(opponent);
           Console.WriteLine("Monster Attacked player");
       }
   }
}

游戏类

游戏类负责实例化玩家和怪兽、记录回合数、判断游戏是否结束,暴露可调用的公共方法给游戏操作类。

class Game
{
   private Character m_player;
   private Character m_monster;
   private int m_round;
   private float playerCriticalPossible = 0.6f;
   private float monsterMissingPossible = 0.2f;
   public Game()
   {
       m_player = new Player(playerCriticalPossible)
       {
           HealthPoint = 15,
           AttackPoint = 2
       };
       m_monster = new Monster(monsterMissingPossible)
       {
           HealthPoint = 20,
           AttackPoint = 6
       };
   }
   public bool IsGameOver => m_monster.HealthPoint == 0 || m_player.HealthPoint == 0;
   public void AttackMonster()
   {            
       m_player.AttackChracter(m_monster);
   }
   public void AttackPlayer()
   {
       m_monster.AttackChracter(m_player);
   }
   public void CurePlayer()
   {
       m_player.Cure();
   }
   public void BeginNewRound()
   {
       m_round++;
   }
   public void ShowGameState()
   {
       Console.WriteLine("".PadLeft(20, '-'));
       Console.WriteLine("Round:{0}", m_round);
       Console.WriteLine("player health:{0}", "".PadLeft(m_player.HealthPoint, '*'));
       Console.WriteLine("monster health:{0}", "".PadLeft(m_monster.HealthPoint, '*'));
   }
}

游戏操作类

在我们这个简易游戏中,没有UI代码,游戏操作类负责在用户输入和游戏中搭建一个桥梁,解释用户的输入。

class GameRunner
{
   private Game m_game;
   public GameRunner(Game game)
   {
       m_game = game;
   }
   public void Run()
   {
       while (!m_game.IsGameOver)
       {
           m_game.BeginNewRound();
           bool validSelection = false;
           while (!validSelection)
           {
           m_game.ShowGameState();
               Console.WriteLine("Make your choice: 1. attack 2. Cure");
               var str = Console.ReadLine();
               if (str.Length != 1)
               {
                   continue;
               }
               switch (str[0])
               {
                   case '1':
                       {
                           validSelection = true;
                           m_game.AttackMonster();
                           break;
                       }
                   case '2':
                       {
                           validSelection = true;
                           m_game.CurePlayer();
                           break;
                       }
                   default:
                       break;
               }
           }
           if(!m_game.IsGameOver)
           {
               m_game.AttackPlayer();
           }
       }            
   }
}

客户端

客户端的代码就非常简单了,只需要实例化一个游戏操作类,然后让其运行就可以了。

class Program
{
   static void Main(string[] args)
   {
       Game game = new Game();
       GameRunner runner = new GameRunner(game);
       runner.Run();
   }
}

试着运行一下,

C#备忘录人生存档的设计模式实例

看起来一切都好。

加上存档

虽然游戏可以正常运行,但是总感觉还是少了点什么。嗯,存档功能,一个游戏没有存档是不健全的,毕竟,人生虽然没有存档,但是游戏可是有的!让我们加上存档功能吧,首先想想怎么设计。

需要存档的数据

首先我们要明确,有哪些数据是需要存档的,在这个游戏中,玩家的生命值、攻击力、暴击率;怪兽的生命值、攻击力和丢失率,游戏的回合数,都是需要存储的对象。

存档定义

这是一个需要仔细思考的地方,一般来说,需要考虑以下几个地方:

  • 存档需要访问一些游戏中的私有字段,比如暴击率,需要在不破坏游戏封装的情况下实现这个功能

  • 存档自身需要实现信息隐藏,即除了游戏,其他类不应该访问存档的详细信息

  • 存档不应该和游戏存放在一起,以防不经意间游戏破坏了存档数据,应该有专门的类存放存档

备忘录模式出场

这个时候应该是主角出场的时候了。看看备忘录模式的定义

在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态

再看看UML,

C#备忘录人生存档的设计模式实例

看起来完全符合我们的需求啊,Originator就是游戏类,知道如何创造存档和从存档中恢复状态,Memento类就是存档类,Caretaker是一个新类,负责保存存档。

经过思考,我们决定采取备忘录模式,同时加入以下措施:

  • 将存档定义为游戏中的私有嵌套类,这样存档可以毫无压力的访问游戏中的私有字段,同时外界永远没有办法去实例化或者尝试通过转型来获得这个类,完美的保护了存档类

  • 存档类是一个简单的数据集合,不包含任何其他逻辑

  • 添加一个存档管理器,可以放在游戏操作类中,可以通过它看到我们当前有没有存档

  • 存档放在存档管理器中

  • 存档实现一个空接口,在存档管理器中以空接口形式出现,这样外部类在访问存档的时候,仅能看到这个空接口。而在游戏类内部,我们在使用存档之前先通过向下转型实现类型转换(是的,向下转型不怎么好,但是偶尔可以用一下)

空接口

interface IGameSave
{
}

私有嵌套存档类

该类存放在game里面,无压力地在不破坏封装的情况下访问game私有字段

private class GameSave : IGameSave
{
   public int PlayerHealth { get; set; }
   public int PlayerAttack { get; set; }
   public float PlayerCritialAttackPossible { get; set; }
   public int MonsterHealth { get; set; }
   public int MonsterAttack { get; set; }
   public float MonsterMissingPossible { get; set; }
   public int GameRound { get; set; }
}

创建存档和从存档恢复

在game中添加创建存档和从存档恢复的代码,在从存档恢复的时候,使用了向下转型,因为从存档管理器读出来的只是空接口而已

public IGameSave CreateSave()
{
   var save = new GameSave()
   {
       PlayerHealth = m_player.HealthPoint,
       PlayerAttack = m_player.AttackPoint,
       PlayerCritialAttackPossible = playerCriticalPossible,
       MonsterAttack = m_monster.AttackPoint,
       MonsterHealth = m_monster.HealthPoint,
       MonsterMissingPossible = monsterMissingPossible,
       GameRound = m_round
   };
   Console.WriteLine("game saved");
   return save;
}
public void RestoreFromGameSave(IGameSave gamesave)
{
   GameSave save = gamesave as GameSave;
   if(save != null)
   {
       m_player = new Player(save.PlayerCritialAttackPossible) { HealthPoint = save.PlayerHealth, AttackPoint = save.PlayerAttack };
       m_monster = new Player(save.MonsterMissingPossible) { HealthPoint = save.MonsterHealth, AttackPoint = save.MonsterAttack };
       m_round = save.GameRound;
   }
   Console.WriteLine("game restored");
}

存档管理器类

添加一个类专门管理存档,此类非常简单,只有一个存档,要支持多存档可以考虑使用List

class GameSaveStore
   {
       public IGameSave GameSave { get; set; }
   }

在游戏操作类添加玩家选项

首先在游戏操作类中添加一个存档管理器

private GameSaveStore m_gameSaveStore = new GameSaveStore();

接着修改Run方法添加用户操作

public void Run()
{
   while (!m_game.IsGameOver)
   {
       m_game.BeginNewRound();
       bool validSelection = false;
       while (!validSelection)
       {
           m_game.ShowGameState();
           Console.WriteLine("Make your choice: 1. attack 2. Cure 3. Save 4. Load");
           var str = Console.ReadLine();
           if (str.Length != 1)
           {
               continue;
           }
           switch (str[0])
           {
               case '1':
                   {
                       validSelection = true;
                       m_game.AttackMonster();
                       break;
                   }
               case '2':
                   {
                       validSelection = true;
                       m_game.CurePlayer();
                       break;
                   }
               case '3':
                   {
                       validSelection = false;
                       m_gameSaveStore.GameSave = m_game.CreateSave();
                       break;
                   }
               case '4':
                   {
                       validSelection = false;
                       if(m_gameSaveStore.GameSave == null)
                       {
                           Console.WriteLine("no save to load");
                       }
                       else
                       {
                           m_game.RestoreFromGameSave(m_gameSaveStore.GameSave);
                       }
                       break;
                   }
               default:
                   break;
           }
       }
       if(!m_game.IsGameOver)
       {
           m_game.AttackPlayer();
       }
   }            
}

注意,上面的3和4是新添加的存档相关的操作。试着运行一下。

C#备忘录人生存档的设计模式实例

看起来一切正常,这样我们就使用备忘录模式,完成了存档读档的功能。

来源:https://www.cnblogs.com/deatharthas/p/13191269.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com