views:

185

answers:

4

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?

A: 

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 ...

Frederik Gheysels
+1  A: 

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.

Mendelt
A: 

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!");
    }
  }
}
John Feminella
This does not make the Eats property any more type safe as it was in the original question. Should you feed the Lion with Grass, you still do not get compiler error. Furthermore, it is not false dilemma but covers real life scenario.
Mr. Lame
+1  A: 

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.

Will
Actually, you did not answer the correct question, it is about the parallel inheritance chain problem. (Animal kingdom is just the typical textbook example for polymorphism). If complex inheritance things are in concern, I would go for ontologies. But I reward your humorous examples!
Mr. Lame
The answer was already given and accepted. I was more interested in showing an alternative design. Inheritance is powerful, but easily abused.
Will