Generics 并行继承链中的多态性与类型安全

Generics 并行继承链中的多态性与类型安全,generics,inheritance,polymorphism,strong-typing,Generics,Inheritance,Polymorphism,Strong Typing,我有两个平行的继承链: Chain1: Animal <- Lion <- Gazelle Chain2: Food <- Meat <- Grass 但是,此代码不是类型安全的。如果我用草喂我的狮子,我只会在运行时遇到我的bug 有人能给我提供一个代码示例,在不牺牲多态性的情况下使用泛型来促进类型安全吗?嗯,也许您可以修改您的第一个继承链: 畜生 -食肉动物 -狮子 -老虎 - ... -食草动物 -羊 然后,你可以这样做: publ

我有两个平行的继承链:

Chain1:
Animal <- Lion
       <- Gazelle

Chain2:
Food   <- Meat
       <- Grass
但是,此代码不是类型安全的。如果我用草喂我的狮子,我只会在运行时遇到我的bug


有人能给我提供一个代码示例,在不牺牲多态性的情况下使用泛型来促进类型安全吗?

嗯,也许您可以修改您的第一个继承链:

畜生 -食肉动物 -狮子 -老虎 - ... -食草动物 -羊

然后,你可以这样做:

public class Animal<T> where T : Food
{
    public abstract T Eats { get; set; }
}

public class Carnivore : Animal<Meat>
{
   ...
}
公共级动物,其中T:食物
{
公共抽象T{get;set;}
}
公共级食肉动物:动物
{
...
}

我还没有测试,这只是我的想法…

动物可以是一个通用类:

public abstract class Animal<T> where T : Food
{
    public abstract T Eats {get;set;}
}
公共抽象类动物,其中T:Food
{
公共抽象T{get;set;}
}
然后你可以把狮子变成这样的肉食动物

public class Lion : Animal<Meat>
{
    //etc...
}    
公共类狮子:动物
{
//等等。。。
}    

但这并非最佳解决方案。您不能再将animal用作多态接口,因为要使用它,您需要了解有关其实现的详细信息。这可能不是多态性的地方。

我认为这是一个错误的两难选择。食物似乎更接近于一个接口而不是一个抽象的基类,因为它听起来根本不像肉和草。取而代之的是,考虑一下:

public interface IFood {
  public boolean IsForCarnivores();
}

public class Lion : Animal {
  ...
  public override IFood Eats
  {
    get { ... }
    set 
    {
      if (value.IsForCarnivores()) DoSomething(value);
      else throw new Exception("I can't eat this!");
    }
  }
}

使用组合而不是继承:

与其基于消化系统进行继承,不如将消化分解为自己的一组类。
首先,一个描述不同饮食方式的界面

public interface IDigest
{
  void Eat(Meat food);
  void Eat(Plant food);
  void Eat(Offal food); //lol nethack
}
食肉动物吃肉,有时吃草药,不喜欢吃垃圾:

public class Carnivorous : IDigest
{ 
  public void Eat(Meat food)
  {
    Console.Write("NOM NOM");
  }
  public void Eat(Plant food)
  {
    if(Starving)
     Console.Write("Ugh, better than nothing.");
    else
      Vomit();
  }
  public void Eat(Offal food)
  {
    Vomit();
  }
}
食草动物很挑剔,宁愿死也不愿吃肉(我知道,保留你的评论,这就是一个例子)

杂食动物什么都吃。见证一场州博览会

public class Omnivorous : IDigest
{ 
  public void Eat(Meat food)
  {
    Console.Write("NOM NOM");
  }
  public void Eat(Plant food)
  {
    Console.Write("NOM NOM");
  }
  public void Eat(Offal food)
  {
    Console.Write("NOM NOM");
  }
}
所有动物都必须进食,因此它们必须有消化系统和其他系统

public abstract class Animal
{
  /* lots of other stuff */
  public IConsume DigestiveSystem {get;set;}
  /* lots of other stuff */
}
嬉皮士是一种具有已知品味的动物;它在实例化时进行自我配置。也可以从外部注入行为和系统

public class Hippie : Animal
{
  public Hippie()
  {
    /*stuff*/
    DigestiveSystem = new Herbivore();
    BodyOdorSystem = new Patchouli();
    /*more stuff*/
  }
}
最后,让我们看看嬉皮士吃汉堡

public static void Main()
{
  Hippie dippie = new Hippie();
  Meat burger = new Meat("Burger", 2/*lb*/);
  dippie.DigestiveSystem.Eat(burger);
}
在为动物等复杂系统建模时,我更喜欢组合而不是继承。复杂系统可以快速分解继承树。以三种动物系统为例:杂食/食草动物/食肉动物、水/空气/陆地和夜间/日间。我们甚至不用担心如何决定哪种分类成为动物的第一个区别点。我们是先把动物引向食肉动物,先引向水生物,还是先引向夜间活动

由于杂食动物可以生活在空气中,喜欢夜间活动(蝙蝠*),也可以是白天行走的陆地生物(人类),因此你必须有一条符合每个选项的遗传路径。这是一个已经有54种不同类型的继承树(它很早,很好)。动物比这复杂得多。您可以很容易地得到一个具有数百万类型的继承树。绝对是合成而不是继承


*例如,新西兰短尾蝙蝠是杂食性的

狮子不吃草?至少我们的猫过去偶尔吃草;-)这不会使Eats属性像原始问题中那样具有任何类型安全性。如果你给狮子喂草,你仍然不会得到编译错误。此外,这不是错误的困境,而是涵盖了现实生活中的场景。实际上,您没有回答正确的问题,这是关于并行继承链的问题。(动物王国只是多态性的典型教科书例子)。如果要考虑复杂的继承问题,我会选择本体论。但我奖励你幽默的例子!答案已经给出并被接受。我对展示另一种设计更感兴趣。继承很强大,但很容易被滥用。
public class Hippie : Animal
{
  public Hippie()
  {
    /*stuff*/
    DigestiveSystem = new Herbivore();
    BodyOdorSystem = new Patchouli();
    /*more stuff*/
  }
}
public static void Main()
{
  Hippie dippie = new Hippie();
  Meat burger = new Meat("Burger", 2/*lb*/);
  dippie.DigestiveSystem.Eat(burger);
}