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

发布于 2024-07-14 19:21:42 字数 679 浏览 9 评论 0原文

我有两个并行的继承链:

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

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

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

I have two parallel inheritance chains:

Chain1:
Animal <- Lion
       <- Gazelle

Chain2:
Food   <- Meat
       <- Grass

I want to implement the "Eats" polymorphic property on Animal. This is how it looks like:

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

However, this code is not type safe. Should I feed my Lion with grass, I will be confronted with my bug only in runtime.

Could someone provide me with a code example that facilitates type safety using Generics without sacrificing polymorphism?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

晨与橙与城 2024-07-21 19:21:42

使用组合而不是继承:

不要根据消化系统进行继承,而是将消化分解为自己的一组类。
首先,一个描述不同饮食方式的界面。

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

当对动物等复杂系统进行建模时,我更喜欢组合而不是继承。 复杂的系统可以快速地爆炸继承树。 以三种动物系统为例:杂食动物/草食动物/肉食动物,水/空气/陆地,以及夜间/白天。 我们甚至不用担心如何决定哪个分类成为动物的第一个区分点。 我们是否首先将动物延伸到食肉动物,首先延伸到水生动物,还是首先延伸到夜行动物?

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

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

Using composition over inheritance:

Instead of inheriting based on digestive system, break off digestion into its own set of classes.
First, an interface that describes different ways to eat.

public interface IDigest
{
  void Eat(Meat food);
  void Eat(Plant food);
  void Eat(Offal food); //lol nethack
}

Carnivores eat meat, can eat herbs sometimes, and don't like crap:

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

Herbivores are picky and would rather die than eat meat (I know, save your comments, this is an example)

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

Omnivores eat anything. Witness a state fair.

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

All animals must eat, so they must have a digestive system, along with other systems.

public abstract class Animal
{
  /* lots of other stuff */
  public IConsume DigestiveSystem {get;set;}
  /* lots of other stuff */
}

Hippies are an animal class with known tastes; it configures itself on instantiation. It is also possible to inject behaviors and systems from the outside.

public class Hippie : Animal
{
  public Hippie()
  {
    /*stuff*/
    DigestiveSystem = new Herbivore();
    BodyOdorSystem = new Patchouli();
    /*more stuff*/
  }
}

And finally, let's see a hippie eat a burger.

public static void Main()
{
  Hippie dippie = new Hippie();
  Meat burger = new Meat("Burger", 2/*lb*/);
  dippie.DigestiveSystem.Eat(burger);
}

When modeling complex systems like animals, I'd prefer composition over inheritance ANY DAY. Complex systems can explode an inheritance tree with the quickness. Take three animal systems: omnivore/herbivore/carnivore, water/air/land, and nocturnal/diurnal. Let's not even worry about how to decide which classification becomes the first point of differentiation for Animals. Do we extend Animal to Carnivore first, to WaterLiving first, or to Nocturnal first?

Since an omnivore can live in the air and prefer the night (bat*) and also be a day walking land creature (humans), you have to have an inheritance path that hits every single option. That's an inheritance tree with 54 different types already (its early, be kind). And Animals are much more complex than this. You could easily get an inheritance tree that had millions of types. Composition over inheritance, definitely.

*New Zealand Short Tailed bat, for example, is omnivorous.

谁的年少不轻狂 2024-07-21 19:21:42

动物可以是一个通用类:

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

然后你可以让狮子成为这样的食肉动物

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

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

Animal can be a generic class:

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

then you can make lion a meat eating animal like this

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

But this is not be an optimal solution. You can't use animal as a polymorphic interface any more because you need to know details about it's implementation to use it. This might just not be the place for polymorphism.

不醒的梦 2024-07-21 19:21:42

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

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

那么,你也许可以做这样的事情:

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

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

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

Hmm, maybe you could modify your first inheritance chain:

Animal
- Carnivore
- Lion
- Tiger
- ...
- Herbivore
- Sheep

Then, you could maybe do something like this:

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

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

I haven't tested, it is just an idea that I have ...

守望孤独 2024-07-21 19:21:42

我认为这有点虚假的困境。 食物似乎比抽象基类更接近接口,因为听起来肉与草根本不相似。 相反,请考虑以下内容:

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

I think this is a bit of a false dilemma. Food seems to be closer to an interface than an abstract base class, since it doesn't sound like Meat is going to be very similar to Grass at all. Instead, consider something like:

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!");
    }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文