Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# “我的rpg外观的单一责任原则(SRP)和类结构”;“奇怪”;_C#_Design Patterns_Solid Principles - Fatal编程技术网

C# “我的rpg外观的单一责任原则(SRP)和类结构”;“奇怪”;

C# “我的rpg外观的单一责任原则(SRP)和类结构”;“奇怪”;,c#,design-patterns,solid-principles,C#,Design Patterns,Solid Principles,我正在做一个角色扮演游戏,只是为了好玩和了解更多关于坚实的原则。我首先关注的是SRP。我有一个代表游戏中角色的“角色”类。它有名字、生命值、法力、能力核心等 现在,通常我也会在我的角色类中放置方法,所以它看起来像这样 public class Character { public string Name { get; set; } public int Health { get; set; } public int Mana { get; set;

我正在做一个角色扮演游戏,只是为了好玩和了解更多关于坚实的原则。我首先关注的是SRP。我有一个代表游戏中角色的“角色”类。它有名字、生命值、法力、能力核心等

现在,通常我也会在我的角色类中放置方法,所以它看起来像这样

   public class Character
   {
      public string Name { get; set; }
      public int Health { get; set; }
      public int Mana { get; set; }
      public Dictionary<AbilityScoreEnum, int>AbilityScores { get; set; }

      // base attack bonus depends on character level, attribute bonuses, etc
      public static void GetBaseAttackBonus();  
      public static int RollDamage();
      public static TakeDamage(int amount);
   }
public class CharacterActions
{
    public static void GetBaseAttackBonus(Character character);
    public static int RollDamage(Character character);
    public static TakeDamage(Character character, int amount);
}
请注意,我现在必须在所有CharacterActions方法中包含正在使用的角色对象。这是利用SRP的正确方式吗?这似乎完全违背了面向对象的封装概念

还是我在这里做错了什么


我喜欢的一点是,我的Character类非常清楚它的作用,它只是表示一个Character对象。

更新-我重做了我的答案,因为经过半个晚上的睡眠,我真的觉得我以前的答案不是很好

为了看到SRP在行动中的一个例子,让我们考虑一个非常简单的字符:

public abstract class Character
{
    public virtual void Attack(Character target)
    {
        int damage = Random.Next(1, 20);
        target.TakeDamage(damage);
    }

    public virtual void TakeDamage(int damage)
    {
        HP -= damage;
        if (HP <= 0)
            Die();
    }

    protected virtual void Die()
    {
        // Doesn't matter what this method does right now
    }

    public int HP { get; private set; }
    public int MP { get; private set; }
    protected Random Random { get; private set; }
}
现在我们有进展了。角色有时会失误,伤害/命中随着等级的增加而增加(假设STR也增加)。很好,但这仍然很枯燥,因为它没有考虑目标的任何方面。让我们看看是否可以解决这个问题:

public void Attack(Character target)
{
    int chanceToHit = CalculateHitChance(target);
    int hitTest = Random.Next(100);
    if (hitTest < chanceToHit)
    {
        int damage = CalculateDamage(target);
        target.TakeDamage(damage);
    }
}

protected int CalculateHitChance(Character target)
{
    return Dexterity + BaseHitChance - target.Evade;
}

protected int CalculateDamage(Character target)
{
    return Strength * 2 + Weapon.DamageRating - target.Armor.ArmorRating -
        (target.Toughness / 2);
}
这一切都很好,但在
角色
类中进行所有这些数字运算不是有点可笑吗?这是一个相当短的方法;在一个真实的RPG中,这个方法可以扩展到数百行,包括保存抛出和所有其他方式的nerditude。想象一下,你带来了一个新的程序员,他们说:我得到了一个双重打击武器的请求,该武器的伤害应该是正常情况下的两倍;我需要在哪里换车?然后你告诉他,检查
字符
嗯??

更糟糕的是,也许游戏增加了一些新的皱纹,比如,哦,我不知道,背刺奖金,或者其他类型的环境奖金。那么在
角色
类中,你他妈的该如何理解这一点呢?你最终可能会对一些单身汉喊叫,比如:

protected int CalculateDamage(Character target)
{
    // ...
    int backstabBonus = Environment.Current.Battle.IsFlanking(this, target);
    // ...
}
恶心。这太糟糕了。测试和调试这将是一场噩梦。那我们该怎么办?将其从
字符
类中取出。
角色
类应该只知道如何做
角色
在逻辑上应该知道如何做的事情,而计算对目标的确切伤害并不是其中之一。我们将为此制作一个课程:

public class DamageCalculator
{
    public DamageCalculator()
    {
        this.Battle = new DefaultBattle();
        // Better: use an IoC container to figure this out.
    }

    public DamageCalculator(Battle battle)
    {
        this.Battle = battle;
    }

    public int GetDamage(Character source, Character target)
    {
        // ...
    }

    protected Battle Battle { get; private set; }
}
好多了。这个类只做一件事。它照罐头上说的做。我们已经摆脱了单例依赖,所以这个类实际上现在可以测试了,而且感觉更正确,不是吗?现在我们的
角色
可以专注于
角色
动作:

public abstract class Character
{
    public virtual void Attack(Character target)
    {
        HitTest ht = new HitTest();
        if (ht.CanHit(this, target))
        {
            DamageCalculator dc = new DamageCalculator();
            int damage = dc.GetDamage(this, target);
            target.TakeDamage(damage);
        }
    }
}
即使现在,一个
角色
直接调用另一个
角色
TakeDamage
方法还是有点可疑,事实上,你可能只是想让角色“提交”攻击给某种战斗引擎,但我认为这部分最好留给读者作为练习


现在,希望您能理解为什么:

public class CharacterActions
{
    public static void GetBaseAttackBonus(Character character);
    public static int RollDamage(Character character);
    public static TakeDamage(Character character, int amount);
}
…基本上是无用的。怎么了

  • 它没有明确的目的;一般“行动”不是单一的责任
  • 它无法完成
    字符本身无法完成的任何事情
    
  • 它完全取决于
    字符
    ,而不是其他字符
  • 它可能需要您公开真正需要私有/保护的
    字符
    类的部分
CharacterActions
类打破了
字符
的封装,并且几乎没有添加任何内容。另一方面,
DamageCalculator
类提供了一个新的封装,并通过消除所有不必要的依赖项和不相关的功能,帮助恢复原始
Character
类的内聚性。如果我们想改变计算损失的方法,很明显我们应该去哪里寻找


我希望这能更好地解释这个原理。

SRP并不意味着类不应该有方法。您所做的是创建一个数据结构,而不是创建一个多态对象。这样做是有好处的,但在这种情况下可能不是有意的,也不是必要的

判断对象是否违反SRP的一种方法是查看对象中的方法使用的实例变量。如果有一组方法使用某些实例变量,但没有使用其他实例变量,那么这通常表明可以根据实例变量组来拆分对象

另外,您可能不希望您的方法是静态的。您可能希望利用多态性——根据调用方法的实例的类型在方法中执行不同操作的能力


例如,如果您有
ElfCharacter
WizardCharacter
,您的方法是否需要更改?如果你的方法绝对不会改变,并且是完全自包含的,那么也许静态是可以的。。。但即使这样,测试也变得更加困难。

我在设计类时使用的方法,也是OO所基于的方法,就是对象对真实世界的对象建模

让我们来看看角色。。。 设计一个角色类可能非常有趣。 可能是合同 鱼类 这意味着任何想要成为角色的东西都应该能够执行Walk()、Talk()、Attack(),并具有一些属性,如生命值、法力值

你可以有一个向导,一个有特殊属性的向导
public abstract class Character
{
    public virtual void Attack(Character target)
    {
        HitTest ht = new HitTest();
        if (ht.CanHit(this, target))
        {
            DamageCalculator dc = new DamageCalculator();
            int damage = dc.GetDamage(this, target);
            target.TakeDamage(damage);
        }
    }
}
public class CharacterActions
{
    public static void GetBaseAttackBonus(Character character);
    public static int RollDamage(Character character);
    public static TakeDamage(Character character, int amount);
}
public interface ICharacter
{
    //...
    IEnumerable<IAction> Actions { get; }
}

public interface IAction
{
    ICharacter Character { get; }
    void Execute();
}

public class BaseAttackBonus : IAction
{
    public BaseAttackBonus(ICharacter character)
    {
        Character = character;
    }

    public ICharacter Character { get; private set; }   

    public void Execute()
    {
        // Get base attack bonus for character...
    }
}