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

时间:2009-03-11 10:45:43

标签: generics inheritance polymorphism strong-typing

我有两个并行的继承链:

Chain1:
Animal <- Lion
       <- Gazelle

Chain2:
Food   <- Meat
       <- Grass

我想在Animal上实现“Eats”多态属性。这就是它的样子:

public abstract class Animal
{
  public abstract Food Eats { get; set;}
}


public class Lion : Animal
{
  public override Food Eats
  {
     get { return new Meat();}
     set 
     {
       if (value is Meat) DoSomething(value);
       else throw new Exception("Lions only eat meat. " + 
                                "You better learn that, dude!");
     }
  }
}

但是,此代码不是类型安全的。如果我用狮子喂狮子,我只会在运行时面对我的虫子。

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

4 个答案:

答案 0 :(得分:3)

使用组合而不是继承:

不是基于消化系统继承,而是将消化分解为自己的一类 首先,描述不同吃法的界面。

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 Herbivorous : IDigest
{ 
  public void Eat(Meat food)
  {
    Vomit();
  }
  public void Eat(Plant food)
  {
    Console.Write("NOM NOM");
  }
  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);
}

在对像动物这样的复杂系统进行建模时,我更喜欢任意组合而不是继承。复杂系统可以快速爆炸继承树。采取三种动物系统:杂食动物/食草动物/食肉动物,水/空气/陆地,以及夜间/昼夜。让我们甚至不担心如何决定哪种分类成为动物的第一个分化点。我们首先将Animal扩展到Carnivore,首先扩展到WaterLiving,还是先将Nocturnal扩展?

由于杂食动物可以生活在空中并且喜欢夜晚(蝙蝠*)并且也是一天行走的陆地生物(人类),你必须拥有一个可以击中每一个选项的继承路径。这是一个有54种不同类型的继承树(早期的,善良的)。动物比这复杂得多。您可以轻松获得具有数百万种类型的继承树。遗传的构成,绝对是。

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

答案 1 :(得分:1)

动物可以是通用类:

public abstract class Animal<T> where T : Food
{
    public abstract T Eats {get;set;}
}
那么你可以让狮子成为像这样吃肉的动物

public class Lion : Animal<Meat>
{
    //etc...
}    

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

答案 2 :(得分:0)

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

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

然后,你可以这样做:

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

public class Carnivore : Animal<Meat>
{
   ...
}

我还没有测试过,这只是一个想法,我有......

答案 3 :(得分:-1)

我认为这是一个虚假的困境。食物似乎更接近界面而不是抽象的基类,因为它听起来并不像Meat会与Grass非常相似。相反,请考虑以下内容:

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!");
    }
  }
}