class design question

C

Calum Grant

grahamo said:
This came up in an interview I did a while ago and I wanted to know
the correct answer. The setup is this;

If I have a base class "food" and also two classes "meat" and "veg"
that inherit from food, thus;

food
/ \
/ \
meat veg


Also I have a base class human, and all humans eat so there's a method
eat defined thus (* denotes the method eat)


human *----void (eat& food)
/ \
/ \
vegetarian carnivore



Now the question is how do we work things such that a vegaterian will
never eat meat, in terms of our class design etc. There's probably a
million and one answers but I'd like to get the best one from the
experts on this group

At first sight, inheritance might seem a suitable tool for this problem.

struct Carnivore
{
virtual void eat(Meat&)=0;
};

struct Herbivore
{
virtual void eat(Herbs&)=0;
};

struct Omnivore : public Carnivore, public Herbivore
{
};

Great! Omnivores eat both types of food.

But, what if we had a composite food containing both Meat and Herbs?

struct Pie : public Meat, public Herbs
{
};

An omnivore could eat a pie (but it would have to be cast as Meat or
Herbs), but you can feed a Pie to a Herbivore, which is perhaps not the
intended behaviour.

One could have quite complicated rules about what can eat what, and one
needs to be able to deal with exceptions (e.g. I am an omnivore, but I
don't eat tomatoes, nuts or monkey). I don't think inheritance can
represent such rules.

So you might need to take a step back and just write

struct Eater
{
virtual void eat(Food&)=0;
};

and derive class hierarchies from Eater and Food. Eater will check the
food using dynamic_casts, and throw exceptions (or perform other
actions) depending on the food type. It's not so pretty but it is
flexible and will work.
 
P

Phil Staite

Now the question is how do we work things such that a vegaterian will
never eat meat, in terms of our class design etc. There's probably a
million and one answers but I'd like to get the best one from the
experts on this group

I would say it is an incomplete class design or at least a poor scheme...

However, to better model/mimic real life, humans eat food, period. So I
would have an eat method on the human class that takes food. I'd
specialize human into vegitarian, carnivore (are there any?) and
omnivore. A vegitarian would just have to deal with being "offered"
meat to eat. It could possibly reject it. You could also model the
real world by giving humans a method to "advertise" their preference.
That way some entity preparing food for a human could query it so as to
find out if it were vegitarian or not.
 
M

Martijn Mulder

This came up in an interview I did a while ago and I
wanted to know the correct answer. The setup is this;

If I have a base class "food" and also two classes "meat"
and "veg" that inherit from food, thus;

food
/ \
/ \
meat veg


Also I have a base class human, and all humans eat so
there's a method eat defined thus (* denotes the method
eat)


human *----void (eat& food)
/ \
/ \
vegetarian carnivore



Now the question is how do we work things such that a
vegaterian will never eat meat, in terms of our class
design etc. There's probably a million and one answers
but I'd like to get the best one from the experts on this
group


thanks much and have a nice weekend.

GrahamO

#include<iostream>
#include<iterator>
#include<string>
#include<vector>

using std::cout;
using std::string;
using std::vector;
using std::iterator;

struct Food{virtual string what()=0;};
struct Meat:Food{string what(){return"Saucage";}};
struct Weed:Food{string what(){return"Cabbage";}};
struct Human
{
vector<Food*>stomach;
virtual void eat(Food&a){stomach.push_back(&a);}
void puke()
{
for(vector<Food*>::iterator a=stomach.begin();a!=stomach.end();a++)
cout<<'\t'<<(*a)->what();
}
};
struct Carnivore:Human{};
struct Vegetarian:Human
{
void eat(Meat&){}
void eat(Weed&a){Human::eat(a);}
};
int main()
{
Carnivore meatball;
Vegetarian veggie;
Meat meat;
Weed weed;
meatball.eat(meat);
meatball.eat(weed);
veggie.eat(meat);
veggie.eat(weed);
cout<<"\nMeatball had: ";
meatball.puke();
cout<<"\nVeggie had: ";
veggie.puke();
return 0;
}
 
E

EventHelix.com

The way Graham0 specified the problem, it does seem to violate LSP.

Both the class hierarchies fail on the substitution test.

Deepa
 
A

Alf P. Steinbach

* EventHelix.com:
The way Graham0 specified the problem, it does seem to violate LSP.

Both the class hierarchies fail on the substitution test.

Nope.

True: in natural language a "carnivore" is not necessarily a "human".

False: a "carnivore" in the class hierarchy is necessarily something more than
a class derived from the class "human".

Do not expect a C++ "display" class to necessarily have any of the properties
of a physical display.

Do not expect a C++ "human" class to necessarily have any of the properties
of a physical human. :)

Hth.,

- Alf
 
D

davidrubin

Phil said:
I would say it is an incomplete class design or at least a poor
scheme...

This is apparantly a very good observation.

After consulting with some collegues, I learned a very nice solution to
this problem. Look at the problem again:

[slightly modified]
food
/ \
/ \
meat vegetable

Also I have a base class human, and all humans eat so there's a
method eat defined thus (* denotes the method eat)

human [void eat(food&)]
/ \
/ \
vegetarian carnivore

Now the question is how do we work things such that a vegaterian
will never eat meat, in terms of our class design etc.

The problem is that you have a method 'eat' in 'human' which cannot be
satisfied by all subtypes. Since 'eat' returns 'void' it is a no-fail
operation. However, 'vegetarian's cannot 'eat' if 'food' is 'meat'.
Therefore, 'vegetarian' is a non-meat-eating human. The way you solve
this is a two-step approach. First, you break the class hierarchy to
indicate that vegetarians only eat 'vegetable's:

vegetarian carnivore
| |
| |
| V
| human
| [void eat(food&)]
|
V
non-meat-eating-human
[void eat(vegetable&)]

Then, you recombine the protocols, with a name-change, into a protocol
hierarchy:

vegetarian carnivore
| |
| |
| V
| meat-eating-human
| [void eatAnything(food&)]
| /
| /
| /
V \/_
human
[void eat(vegetable&)]

This hierarchy enforces the rule that while all 'humans' eat vegetables
('vegetarian's in particular), only 'meat-eating-humans' 'eat' meat in
addition to 'vegetable's. Now, 'eat' is a no-fail operation that can be
satisfied by all subtype. (Of course, the subtype of
'meat-eating-human' is called 'carnivore', which suggests that
instances of this class only eat meat, but it is easy to see how the
transformation can be extended to include omnivores, and specialize
'carnivore').

/david
 
D

davidrubin

Ioannis said:

Every time you want to test some aspect of 'food', you have to
dynamic_cast the instance to some new 'food' subtype (e.g., is the food
sugar-free AND fat-free AND salt-free, etc). This tightly couples the
two class hierarchies because various 'human' subtypes have to know
about many 'food' subtypes. The first solution I gave moves all these
dependencies into one component, which is a bit better. However, the
whole problem can be refactored (as I show else-thread) to enforce more
rules at compile-time. /david
 
I

Ioannis Vranos

Every time you want to test some aspect of 'food', you have to
dynamic_cast the instance to some new 'food' subtype (e.g., is the food
sugar-free AND fat-free AND salt-free, etc). This tightly couples the
two class hierarchies because various 'human' subtypes have to know
about many 'food' subtypes. The first solution I gave moves all these
dependencies into one component, which is a bit better. However, the
whole problem can be refactored (as I show else-thread) to enforce more
rules at compile-time. /david


I think the sub-types that you mentioned are not real sub-types but
additional "properties" of food, and these details depend on what
situation the OP is modelling (for example a restaurant?).
 
K

Karl Heinz Buchegger

Ioannis said:
In the code it was pointed out that pointers could be used, and there is
a catch() block for the exception.

My bad.
I missed it completely.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
474,202
Messages
2,571,055
Members
47,658
Latest member
jaguar32

Latest Threads

Top