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

WHe*_*ann 6 c# polymorphism inheritance design-patterns

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

所以假设我有这两个抽象类:CharacterWeapon.

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

abstract class Character
{
    public Weapon weapon { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

派生类可能是SwordStaffWeapon,并WarriorMageCharacter等(这是所有假设,而不是与我的实际的软件!).

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

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

class Warrior : Character
{
    public Warrior()
    {
        weapon = new Sword();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是这样,每当我想在我的Weapon内部正确使用我的对象时Warrior,我必须执行一个演员,我相信这不是一个很好的练习.此外,我没有办法防止自己弄乱,也就是说,没有类型安全!

一个理想的解决方案是能够使用Weapon weapon属性覆盖Warrior类中的Sword weapon属性,这样我有类型安全性,如果用户使用my Warrior作为a Character,他仍然可以使用my Sword作为Weapon.可悲的是,C#似乎不支持这种结构.

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

谢谢阅读!

Eri*_*ert 12

更新:这个问题是我博客上一系列文章的灵感来源.感谢有趣的问题!

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

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

如果你对这背后的理论感兴趣,你会想要寻找"利斯科夫替代原则".(以及为什么"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; }
}
Run Code Online (Sandbox Code Playgroud)

现在Warrior 的公共表面区域有一个永远是剑的武器,但任何使用该ICharacter界面的人都会看到一个Weapon类型的属性IWeapon.

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

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

它变得一团糟.考虑不要尝试在类型系统中捕获太多.

  • @WHermann:C#有一个相当不错的类型系统.如果你真的有一个多动态调度问题,那么像Dylan这样的语言可以做得很好.如果你想使用泛型,Scala有一些稍微好一点的协方差/逆变支持.Haskell的"更高类型"也非常强大. (2认同)

rdu*_*com 9

只需使用泛型类型约束!这是一个例子

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @WHermann:您想要的功能称为*通用协方差*.C#仅在非常有限的情况下允许协方差*,而这些情况不是这样.原因当然是推动这个问题的一个问题:一个`Character <Weapon>`可以使用*any*武器,但是`Character <Sword>`只能使用*sword*,因此它们*不兼容类型*.一碗香蕉不是一碗水果; 你可以把一个苹果放入一碗水果中,但你不能把苹果放入一碗香蕉中,并让它留在一碗香蕉中. (2认同)
  • @WHermann:关于这是否是"惯用语"的问题 - 遗憾的是,在C#中使用泛型是一种常见的*,但我不能推荐它.我们想到"一碗水果",所以说你可以将碗通用化为不同类型的水果是有道理的.但我们从不说"武器的特征",这表明仿制药不适合这个商业领域. (2认同)