Any advice on solving this problem?
There's no one best solution. You might want to make this a community wiki, as that's generally the accepted practice for subjective questions.
I'd think about what actually would make the best parent class for the hierarchies, and try to figure out what "best" means in that context.
Everything there are Things, which could have a Name attribute. But everything there at some level is Food; if it can eat something, something can eat it. I might make Food the parent class, and give it a method that returns a boolean to check if the parameter object can eat the current object.
So,
class Food {
boolean canBeEatenBy(Food hungryObject)
String name
}
That seems the simplest class hierarchy that fits everything I might need on a first pass?
That said, the important part in most interview questions is the feel you get for the interviewee, not so much the exact answer that they give.
Well, here's one good one that I came up with - utilizing OOP overriding, subclass and superclass:
namespace Animals{
// base class Animal
class Animal{
public void eat(Food f){
}
}
class Carnivore extends Animal{
public void eat(Meat f){
}
}
class Herbivore extends Animal{
public void eat(Plant f){
}
}
class Omnivore extends Animal{
public void eat(Food f){
}
}
}
namespace Food{
// base class Food
class Food{
}
class Meat extends Food{
}
class Plant extends Food{
}
}
I create subclasses Herbivore, Carnivore and Omnivore from the superclass Animal and override the eat method with the type of food that it can actually eat.
So:
Plant grass = new Plant();
Herbivore deer = new Herbivore();
deer.eat(grass); // ok
Plant grass2 = new Plant();
Carnivore tiger = new Carnivore();
tiger.eat(grass2); // not ok.
Meat deer2 = new Meat();
tiger.eat(deer2); // ok
Well, the final problem is that, when you specify that deer is a Herbivore, you can't make it a Meat for tiger to eat. However at the end of the day, this should be sufficient for solving the interview problem whilst not putting the interviewer to sleep.
There's a wonderful poster for the Liskov Substitution Principle that says, "If it looks like a duck, quacks like a duck, but needs batteries, you've probably got the wrong abstraction." And that's the quick answer - some of the objects can be both animals and food, so unless you're willing to go the route of multiple inheritance, then the classification schema is all wrong.
Once you've cleared that hurdle, the rest is open-ended, and you can bring in other design principles. For instance, you could add an IEdible interface that allows objects to be consumed. You might go aspect-oriented, and add decorators for carnivore and herbivore, and that would allow consumption of only the right class of objects.
The point is to be able to think on your feet, to see and explain various aspects of a problem, and to communicate well. And perhaps not to get stuck on a "one right answer" limitation.
Here's some thoughts on that interview question:
I agree with Cylon Cat: This kind of abstraction doesn't work well without multiple inheritance (even if it's Java-like interfaces.)
I would create two forms of inheritance:
Animal:
- Carnivore
- Herbivore
Food:
- Meat
- Vegetable
The "eat" method of the two kinds of animals (I'm ignoring omnivores, insectivores, and many other kinds) would be specialized for the different kinds of food. If we're using a language like Java, then Food would be an interface.
I'd tell him to scratch that. It's a horrible abstraction. Not to mention we're not given any context. Abstractions don't come out of thin air, or out of an "idea" of what's "right". Show me what problem are you trying to solve first, so we can evaluate this abstraction.
If no context is provided, then I'll just assume/make-up my own: you want some types of objects to be able to eat other types of objects. Nothing more, nothing less.
Make an Eatable interface (or you can call it Food, if you want), and since we have no context what so ever, I'll assume it's a toy console program, that just prints:
<X> ate <Y>
so all we need for this interface is a getFoodName() method.
For error checking, you can create a bunch of isXFoodType methods, for instance, isGrassFoodType(), isMeatFoodType(), etc. The Cow's implementation of Eat(Eatable e) would check for isGrassFoodType(), and when fails, prints:
"Cow can't eat " + e.getFoodName()
Alan Kay, who coined the term "object-oriented programming", has said "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things".
Trying to fix this "problem" in the data model sounds to me like the opposite of late-binding: why do you need the compiler to enforce this? I wouldn't worry about changing the model at all. If you're passed something you can't eat, you throw an exception -- just like in real life, pretty much!
Any animal is food, any vegetable is food. And in fact a tiger can be eaten by a cow. (The prion disease scrapie is spread by feeding infected sheep neural tissue to uninfected sheep.)
You could have a hierarchy of species, ala Linnaeus, both animal and vegetable. Each species is a Singleton, and it has a List<Species> that records its typical diet. Ditch the Food hierarchy entirely, it only confuses things.
And, if your only problem is recording diet for each species, then the multiple Species classes are unnecessary. Just have a single Species class with the species name as one instance variable and the List<Species> as another.
If you expect the system to get very big I would suggest subclassing plant/meat and herbivore/carnivore/omnivore.
Ensure the system has a standard interface for all plant/animals called getFoodName() and getFoodType(), you could enforce this by creating a parent class for plants/animals called species.
The problem I would see with the subclassing plant/meat and carnivore/herbivore is that a meerkat is carnivore but it likely can't eat a rhino(there may be a better example), so some restrictions are needed beyond "I eat meat, you eat plants".
If it wasn't going to get incredibly big and you wanted to be neurotic about it, you could store static enums of allowable foods for each subclass of animal. So tiger could store deer, antelope etc.
Food should be an interface, therefore Plant and Animal could be Food too.
abstract Animal class should have eat method that take Food as parameter.
subclasses of Animal: Carnivore, Herbivore and Omnivore should have their own version of eat.
For example for Carnivore:
private void eat(Food food)
{
if(food instanceof Animal)
{
happilyEat();
}
else
{
sniff&TurnAway();
}
}
The Problems solved.
But for a better design, Carnivore, Herbivore and Omnivore should be interfaces too, as they are not the proper way of tagging the animals.