在继承的类中强制继承成员的类型安全

时间:2015-02-14 23:19:48

标签: c# inheritance design-patterns polymorphism

我正在努力遵守良好的OO设计原则和设计模式等。因此,在开发我的C#应用​​程序时,我经常可以找到多种解决方案来解决设计和架构问题,我总是希望找到并实现更“规范”的解决方案,以期构建高度可维护和灵活的软件,并成为更好的OO程序员

假设我有两个抽象类:CharacterWeapon

abstract class Weapon
{
    public string Name { get; set; }
}

abstract class Character
{
    public Weapon weapon { get; set; }
}

派生类可能是来自Sword的{​​{1}}和Staff,来自Weapon的{​​{1}}和Warrior等等(这都是假设,与我的实际软件无关!)。

每个Mage都有一个Character,但对于Character的每个实现,我都知道它将具有Weapon的实现。例如,我知道(我想强制执行!)在运行时,Character的每个实例都会有Weapon类型Warrior。我当然可以这样做:

Weapon

但是这样,每当我想在Sword内正确使用我的class Sword : Weapon { public void Draw() { } } class Warrior : Character { public Warrior() { weapon = new Sword(); } } 对象时,我必须执行一个演员,我相信这不是一个很好的练习。此外,我没有办法防止自己弄乱,也就是说,没有类型安全!

理想的解决方案是能够使用Weapon属性覆盖Warrior类中的Weapon weapon属性,这样我就可以使用类型安全性,如果用户使用my { {1}}作为Warrior,他仍然可以将Sword weapon用作Warrior。可悲的是,C#似乎不支持这种构造。

所以这是我的问题:这是一种经典的OO问题,有一个名称和一个记录良好的解决方案吗?在那种情况下,我非常想知道问题的名称和解决方案。一些良好阅读材料的链接将非常有用! 如果没有,您会建议采用什么样的课堂设计来保持功能并以优雅和惯用的方式实施类型安全?

感谢阅读!

5 个答案:

答案 0 :(得分:12)

更新:这个问题是a series of articles on my blog的灵感来源。感谢有趣的问题!

  

这是一种经典的OO问题,有一个名字和一个记录良好的解决方案吗?

您遇到的根本问题是,在OO系统中很难将限制添加到子类。说“一个角色可以使用武器”,然后“战士是一种只能使用剑的角色”很难在OO系统中建模。

如果你对这背后的理论感兴趣,你会想要寻找“Liskov替代原则”。 (以及为什么“Square”不是“Rectangle”的子类型,反之亦然。)

然而,有一些方法可以做到这一点。这是一个。

interface ICharacter
{
    public IWeapon Weapon { get; }
}
interface IWeapon {  }
class Sword : IWeapon { }
class Warrior : ICharacter 
{
    IWeapon ICharacter.Weapon { get { return this.Weapon; } }
    public Sword Weapon { get; private set; }
}

现在Warrior的 public 表面区域有一个始终是Sword的武器,但是使用ICharacter接口的任何人都会看到类型为{Weapon的{​​{1}}属性1}}。

这里的关键是摆脱“是一种特殊的”关系,走向“能履行契约”的关系。而不是说“战士是一种特殊的角色”,而是说“战士是一个可以在需要角色时使用的东西”。

所有这一切都说明了,你正在开始一个充满迷人问题的世界,在那里你会发现OO语言实际上代表这些东西有多么糟糕。一旦你开始提出更复杂的问题,例如“当一个战士使用锋利的剑来攻击穿着皮甲的兽人时会发生什么?”,你很快就会发现自己陷入双重调度问题并学习如何用访客模式来解决它。 “并发现你有四个类层次结构 - 角色,武器,怪物,盔甲 - 所有这些都有影响结果的子类。虚方法只允许您在一个运行时类型上进行调度,但是您会发现需要两个,三个,四个或更多级别的运行时类型调度。

它变得一团糟。考虑试图在类型系统中捕获太多。

答案 1 :(得分:9)

只需使用generics type constraint!这是一个例子

public abstract class Weapon
{
    public string Name { get; set; }
}

public interface ICharacter
{
    Weapon GetWeapon();
}

public interface ICharacter<out TWeapon> : ICharacter
    where TWeapon : Weapon
{
    TWeapon Weapon { get; } // no set allowed here since TWeapon must be covariant
}

public abstract class Character<TWeapon> : ICharacter<TWeapon>
    where TWeapon : Weapon
{
    public TWeapon Weapon { get; set; }
    public abstract void Fight();
    public Weapon GetWeapon() { return this.Weapon; }
}

public class Sword : Weapon
{
    public void DoSomethingWithSword()
    {
    }
}

public class Warrior : Character<Sword>
{
    public override void Fight()
    {
        this.Weapon.DoSomethingWithSword();
    }
}

答案 2 :(得分:1)

为了记录和未来的参考,我发布了我可能最终会使用的解决方案:

abstract class Weapon {}

abstract class Character
{
    public abstract Weapon GetWeapon();
}

class Sword : Weapon {}

class Warrior : Character
{
    public Sword Sword { get; private set; }

    public override Weapon GetWeapon()
    {
        return Sword;
    }
}

正如我所看到的,唯一的缺点是Warrior类型的对象具有Sword Sword属性 Weapon GetWeapon()方法。在我的例子中,这只是一个美化问题,因为属性和方法在调用时都会返回对同一对象的引用。

欢迎任何评论!

答案 3 :(得分:1)

问题是&#34;战士只能使用剑&#34;限制不是应该在类型系统中建模的东西。

你有&#34;角色是拥有武器的东西&#34;

abstract class Character {
   public Weapon weapon { get; set; }
}

但你真正想要的是说&#34;字符是有武器的东西,但有一些限制&#34;。我建议没有具体的类型,如&#34; warrior&#34; &#34;剑&#34;根本不是,那些具体的类型应该是实例

sealed class Weapon {
   public string Name { get; set; }
}

sealed class Character {
   const int sSkillToUseWeapon = 50;
   readonly Dictionary<string, int> mWeaponSkills = new Dictionary<string, int>();
   Weapon mCurrentWeapon;

   public Weapon CurrentWeapon {
      get {
         return mCurrentWeapon;
      } set {
         if (!mWeaponSkills.ContainsKey(value.Name))
            return;

         if (mWeaponSkills[value.Name] < mSkillToUseWeapon)
            return;

         mCurrentWeapon = value;
      }
   }

   public void SetWeaponSkill(string pWeaponName, int pSkill) {
      if (mWeaponSkills.ContainsKey(pWeaponName))
         mWeaponSkills[pWeaponName] = pSkill;
      else
         mWeaponSkill.Add(pWeaponName, pSkill);
   }
}

然后你可以声明它们的实例使用和重用:

var warrior = new Character();
warrior.SetWeaponSkill("Sword", 100);
warrior.SetWeaponSkill("Bow", 25);

var archer = new Character();
archer.SetWeaponSkill("Bow", 125);
archer.SetWeaponSkill("Sword", 25);
archer.SetWeaponSkill("Dagger", 55);
//etc...

Warrior是具有特定武器技能的角色实例。

当你以这种方式处理事物时,另一个必然会受到限制的问题是,例如&#34; Rusty Sword&#34;之间存在差异。和#34;生锈的剑&#34;。换句话说,即使&#34; Rusty Sword&#34;是&#34; Weapon&#34;的一个实例,&#34; Rusty Sword&#34;的一般概念和躺在地上的实际生锈的剑之间仍然存在差异。对于#34; Goblin&#34;同样的事情或者&#34;战士&#34;。

我自己使用的术语是&#34; Stock ____&#34;和&#34;放置____&#34;,因此定义&#34;类&#34;的StockWeapon之间存在差异。武器和放置武器,这是一个实际的&#34;实例&#34;游戏世界中的StockWeapon。

然后放置对象&#34;有&#34; Stock对象的实例,它们定义了它们的共同属性(基础损坏,基础耐久性等),并具有其他属性,这些属性定义了它们当前状态,与其他类似项目无关(位置,耐久性损坏等)。

Here是一个关于在游戏编程中使用OOP模式的好网站。(哎呀我想我写了这篇文章的一半,游戏编程部分只是一个类比。但我确实打算希望解释会保持类似。))

答案 4 :(得分:0)

我认为你在Covariant return type的C#中缺乏支持。请注意,您可以将属性的get视为一种特殊的方法。