C#备忘录人生存档的设计模式实例
作者:老胡写代码 发布时间:2022-06-15 12:40:38
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();
}
}
试着运行一下,
看起来一切都好。
加上存档
虽然游戏可以正常运行,但是总感觉还是少了点什么。嗯,存档功能,一个游戏没有存档是不健全的,毕竟,人生虽然没有存档,但是游戏可是有的!让我们加上存档功能吧,首先想想怎么设计。
需要存档的数据
首先我们要明确,有哪些数据是需要存档的,在这个游戏中,玩家的生命值、攻击力、暴击率;怪兽的生命值、攻击力和丢失率,游戏的回合数,都是需要存储的对象。
存档定义
这是一个需要仔细思考的地方,一般来说,需要考虑以下几个地方:
存档需要访问一些游戏中的私有字段,比如暴击率,需要在不破坏游戏封装的情况下实现这个功能
存档自身需要实现信息隐藏,即除了游戏,其他类不应该访问存档的详细信息
存档不应该和游戏存放在一起,以防不经意间游戏破坏了存档数据,应该有专门的类存放存档
备忘录模式出场
这个时候应该是主角出场的时候了。看看备忘录模式的定义
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
再看看UML,
看起来完全符合我们的需求啊,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是新添加的存档相关的操作。试着运行一下。
看起来一切正常,这样我们就使用备忘录模式,完成了存档读档的功能。
来源:https://www.cnblogs.com/deatharthas/p/13191269.html
猜你喜欢
- 前言做APP应用开发的时候,用户头像肯定是必不可少的,但是90%以上的需求头像都是圆形的。那么,如何通过自定义View的方式实现圆形头像呢,
- 目录面试题1:如何判断对象是否存活1.引用计数算法2.可达性分析算法面试题2:哪些对象可以作为GC Roots?面试题3:你了解的对象引用方
- ArrayList集合的创建非泛型创建ArrayList集合对象,可以添加任意Object子类元素至集合//非泛型创建的ArrayList集
- 前言最近想体验下最新版本的SpringBoot,逛了下官网,发现SpringBoot目前最新版本已经是2.6.4了,版本更新确实够快的。之前
- 前面我们完成了与商品类别相关的业务逻辑,接下来我们开始做具体商品部分。1. 数据库建表并映射Model首先我们在数据库中新建一张表,然后使用
- 绑定多个按钮到同一个事件1.添加代码private void clauseElementClicked(object sender, Eve
- 项目信息使用SpringBoot web框架,版本号 2.7.10<dependency><groupId>org.
- 有时,类或方法需要对类型变量加以约束。下面是一个典型的例子,我们要寻找数组中的最小元素:public class ArrayAlg { &n
- 什么事读写分离读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELEC
- 对于一个应用程序而言,控件是搭建用户界面的积木。它们具备交互式的特征。VS 2012中的控件都放在工具箱中,添加一个控件到窗体,只需在工具箱
- 可以静态绑定数据源,这样就自动为DataGridView控件添加 相应的行。假如需要动态为DataGridView控件添加新行,方法有很多种
- Maven Release当我们的项目达到了当前的目标,在经过检测后不需要改变。这时我们就需要将SNAPSHOT版本打包成RELEASE版本
- 在winform里拖入一个datagridview控件,跟一个openfiledialog控件using System;using Syst
- Spring-boot JMS 发送消息慢的问题解决1、在《ActiveMQ 基于zookeeper的主从(levelDB Master/S
- 一、项目搭建1、新建模块2、导入依赖 :将不相关的依赖删掉<!-- <de
- Java画图 给图片底部添加文字标题需求给图片底部添加文字编号import java.awt.Color;import java.awt.F
- 好几天没有跟进Spring MVC的学习了,之前看了点源码都忘的差不多了。这次就跟着之前的问题,继续总结下Spring MVC中的小知识。u
- 缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统开销,提高系统效率
- C# Class写入Json/// <summary> /// 写入jso
- 插件安装方式:新版本IDE安装方式略有不同,不一一赘述 1、Background Image Plus