1

我有两个并行的继承链:

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

4 回答 4

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

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

由于杂食动物可以生活在空中并喜欢夜晚(蝙蝠*),并且还可以是白天行走的陆地生物(人类),因此您必须有一条能够满足每一个选择的继承路径。那是一个已经有 54 种不同类型的继承树(它很早,善良)。动物比这复杂得多。您可以轻松获得具有数百万种类型的继承树。绝对是组合优于继承。

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

于 2009-03-11T12:29:54.057 回答
1

Animal 可以是一个泛型类:

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

那么你可以像这样让狮子成为肉食动物

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

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

于 2009-03-11T10:52:24.417 回答
0

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

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

然后,你也许可以做这样的事情:

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

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

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

于 2009-03-11T10:51:39.317 回答
-1

我认为这是一个错误的困境。Food 似乎比抽象基类更接近接口,因为听起来 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!");
    }
  }
}
于 2009-03-11T11:21:21.133 回答