class design question

G

grahamo

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
 
A

Alf P. Steinbach

* grahamo:
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

There is no best answer.

If you want a vegetarian to spit out any meat it's served, I suggest you check
the dynamic type of food in the vegetarian's override of 'eat'.

If you want a vegetarian to never be offered meat as food in the first place,
I suggest you remove the general 'eat' function, or make it non-public.
 
A

Alf P. Steinbach

* EventHelix.com:
The class hierarchies presented here violates the Liskov Substitution
Principle.

Actually no. Much is left unspecified. With some sets of more rigorous
specifications you get LSP violations, with others not.
 
D

davidrubin

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) [ITYM 'void eat(food& f)'
/ \
/ \
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

The answer is simple. You have a component 'foodtypes', which defines
an enumeration class 'FoodTypes' containing an enumeration, 'FoodType',
of all food types (e.g., MEAT, VEGETABLE, etc). You have another
component, 'fooutil', which defines a set of free functions in the
namespace 'FoodUtil'. One of these is the function 'isVegetable(const
food& f)', returning 'bool'. The 'food' protocol must define a const
virtual function 'identify()' returning 'int'. The 'vegetable' concrete
class implements 'identify' by returning 'FoodType::VEGETABLE' (as an
*int*). 'isVegetarian' is implemented by calling 'identify' on its
argument and switching on the result, which is interpreted as a
'FoodType'. The 'vegetarian' concrete class implements 'eat' by passing
its 'food' argument, 'f' (corrected in the picture above), to
'isVegetable'. If 'f' is a vegetable, 'eat' succeeds.
(Of course the problem is broken because 'eat' has to return a status
in order to know whether the 'vegetarian' ate the 'food'.)

Now, look at the component dependencies:

human key: a o--- b [a uses b in the interface]
o * a *--- b [a uses b in the implementation]
/ \
/ \
food foodutil
* *
\ /
\ /
foodtypes

'foodtypes' is likely to change as new foods are added. However, this
change is only visible to the 'foodutil' implementation (which must
recompile when the 'foodtypes' interface changes). The 'food' protocol
never changes. Nor does the 'human' protocol. 'foodutil' changes slowly
as most 'food's inherit their implementation of 'identify' from 'meat'
or 'vegetable', so most derived 'food's never recompile when
'foodtypes' is changed. This means that as new foods are added, the
application only has to relink, but only rarely recompile.

HTH, /david
 
K

Karl Heinz Buchegger

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) [ITYM 'void eat(food& f)'
/ \
/ \
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

The answer is simple. You have a component 'foodtypes', which defines
an enumeration class 'FoodTypes' containing an enumeration, 'FoodType',
of all food types (e.g., MEAT, VEGETABLE, etc). You have another
component, 'fooutil', which defines a set of free functions in the
namespace 'FoodUtil'. One of these is the function 'isVegetable(const
food& f)', returning 'bool'. The 'food' protocol must define a const
virtual function 'identify()' returning 'int'. The 'vegetable' concrete
class implements 'identify' by returning 'FoodType::VEGETABLE' (as an
*int*). 'isVegetarian' is implemented by calling 'identify' on its
argument and switching on the result, which is interpreted as a
'FoodType'. The 'vegetarian' concrete class implements 'eat' by passing
its 'food' argument, 'f' (corrected in the picture above), to
'isVegetable'. If 'f' is a vegetable, 'eat' succeeds.
(Of course the problem is broken because 'eat' has to return a status
in order to know whether the 'vegetarian' ate the 'food'.)

Wow. That's mighty complicated for avoiding a dynamic_cast.
But in principle you are doing just that: figure out if
the passed food object is of type vegetable.

Much simpler to do:

void Vegetarian::eat( food& meal )
{
if( dynamic_cast< Vegetable* >( &meal ) ) {
// proceed, meal is a vegetable or derived from
// it
}
}

Never duplicate information the compiler can provide you with!
That also means that having some sort of 'Identify Tag' in a class
isn't a very good idea. The compiler already provides you with one.
Just use that, instead of coming up with your own.

But the interesting point is: Can you do the very same thing
without a dynamic_cast?
Well. A possible solution would be to implement some form
of "double dispatch" which resolves to a function returning
true or false for the combination of person-type and food-type.
 
Y

yvinogradov

Can you add an abstract methond IsSuitableForVegetarian to the Food
class and then either swallow food or spit it out depending on the
specific implementation?
 
K

Karl Heinz Buchegger

Can you add an abstract methond IsSuitableForVegetarian to the Food
class and then either swallow food or spit it out depending on the
specific implementation?

Sure you could.
But the question is: Do you want to do this?

The problem is, that now you have some knowledge of Vegetarians
in the Food class and usually this is not a good idea. An abstract
Food class should not have some knowledge that there are special
classes derived from it. (That is eg. one of the problems with
the well known 'double-dispatch' method in C++ which uses a virtual
function just to dispatch a second virtual call based on the this pointer:

#include <iostream>

class B;
class C;

class A
{
public:
virtual void Use( A& X ) = 0;
virtual void Use( B& B ) = 0;
virtual void Use( C& C ) = 0;
};

class B: public A
{
public:
virtual void Use( A& X ) { X.Use( *this ); }
virtual void Use( B& B ) { std::cout << "B - B\n"; }
virtual void Use( C& C ) { std::cout << "B - C\n"; }
};

class C: public A
{
public:
virtual void Use( A& X ) { X.Use( *this ); }
virtual void Use( B& B ) { std::cout << "C - B\n"; }
virtual void Use( C& C ) { std::cout << "C - C\n"; }
};


void foo( A& Obj1, A& Obj2 )
{
// call a function based on the
// runtime type of Obj1 *and* Obj2

Obj1.Use( Obj2 );
}

int main()
{
B MyB;
C MyC;

foo( MyB, MyB );
foo( MyC, MyC );
foo( MyB, MyC );
foo( MyC, MyB );
}

The problem is, that class A needs to have knowledge that there is a class B and
a class C derived from it. Whenever you need to introduce a new class in that
hierarchy, you must not forget to update class A with a new virtual function and
all classes derived vom A with functions handling that. If you don't do it, you
create a nice never ending recursion.
 
D

davidrubin

Karl 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) [ITYM 'void eat(food& f)'
/ \
/ \
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

The answer is simple. You have a component 'foodtypes', which defines
an enumeration class 'FoodTypes' containing an enumeration, 'FoodType',
of all food types (e.g., MEAT, VEGETABLE, etc). You have another
component, 'fooutil', which defines a set of free functions in the
namespace 'FoodUtil'. One of these is the function 'isVegetable(const
food& f)', returning 'bool'. The 'food' protocol must define a const
virtual function 'identify()' returning 'int'. The 'vegetable' concrete
class implements 'identify' by returning 'FoodType::VEGETABLE' (as an
*int*). 'isVegetarian' is implemented by calling 'identify' on its
argument and switching on the result, which is interpreted as a
'FoodType'. The 'vegetarian' concrete class implements 'eat' by passing
its 'food' argument, 'f' (corrected in the picture above), to
'isVegetable'. If 'f' is a vegetable, 'eat' succeeds.
(Of course the problem is broken because 'eat' has to return a status
in order to know whether the 'vegetarian' ate the 'food'.)

Wow. That's mighty complicated for avoiding a dynamic_cast.
But in principle you are doing just that: figure out if
the passed food object is of type vegetable.

Much simpler to do:

void Vegetarian::eat( food& meal )
{
if( dynamic_cast< Vegetable* >( &meal ) ) {
// proceed, meal is a vegetable or derived from
// it
}
}

Well, if you are going to use dynamic_cast, there is really no point in
having polymorphism.
Never duplicate information the compiler can provide you with!
That also means that having some sort of 'Identify Tag' in a class
isn't a very good idea. The compiler already provides you with one.
Just use that, instead of coming up with your own.

Actually, there is no identifier tag. That is the whole point. You have
a virtual function that returns opaque data. The trick is to use the
utility to get meaningful semantics. For example, 'vegetarian' can use
a whole suite of utility functions like 'isVegetable', 'isOvaLacto',
'isSaltFree' etc. In order for you to get the same behavior, your
'vegetarian' class becomes dependent on the entire 'food' class
hierarchy because you have to do a dynamic_cast to every 'food'
subtype. That's wrong.
But the interesting point is: Can you do the very same thing
without a dynamic_cast?

I gave one.
Well. A possible solution would be to implement some form
of "double dispatch" which resolves to a function returning
true or false for the combination of person-type and food-type.

That's what it does. /david
 
I

Ioannis Vranos

EventHelix.com said:
The class hierarchies presented here violates the Liskov Substitution
Principle. The principle states:

In class hierarchies, it should be possible to treat a specialized
object
as if it were a base class object.


Why it violates it?



I guess the LSP must be followed only where it makes sense to do so, I
do not think such silver-bullet rules can exist.
 
I

Ioannis Vranos

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


thanks much and have a nice weekend.

GrahamO


As it has been mentioned in the thread you can either use an enumeration
for food and do:


enum food { meat=1, veg };


// Abstract if you need it to be abstract
class human
{
// ...

public:

// virtual if you need it to be virtual, other wise non-virtual
virtual void eat (food &f)=0;
virtual ~human() {}
// ...
};


class vegetarian: public human
{
// ...

public:

virtual void eat(food &f)
{
if(f==veg)
{
// process food
}

else
// do something
}

// ...
}
// ...
};



or use RTTI with dynamic_cast (perhaps in a pointer conversion, but
since you are using reference here, I will continue using references):


#include <typeinfo>


class food
{
// ...

public:
virtual ~food() {}
};


class veg: public food
{
// ...
public:
//...
};



class vegetarian: public human
{
// ...

public:

virtual void eat(food &f) try
{

veg &rveg= dynamic_cast<veg &>(f);

// Process food
}

catch(std::bad_cast)
{
// Do something
}

// ...
};
 
D

davidrubin

Ioannis said:
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
[snip]
As it has been mentioned in the thread you can either use an enumeration
for food and do:


enum food { meat=1, veg };

Not quite like this. The problem statement specifies that 'food' is a
class.
or use RTTI with dynamic_cast (perhaps in a pointer conversion, but
since you are using reference here, I will continue using
references):

Again, this solution does not scale. /david
 
K

Karl Heinz Buchegger

grahamo wrote:
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) [ITYM 'void eat(food& f)'
/ \
/ \
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

The answer is simple. You have a component 'foodtypes', which defines
an enumeration class 'FoodTypes' containing an enumeration, 'FoodType',
of all food types (e.g., MEAT, VEGETABLE, etc). You have another
component, 'fooutil', which defines a set of free functions in the
namespace 'FoodUtil'. One of these is the function 'isVegetable(const
food& f)', returning 'bool'. The 'food' protocol must define a const
virtual function 'identify()' returning 'int'. The 'vegetable' concrete
class implements 'identify' by returning 'FoodType::VEGETABLE' (as an
*int*). 'isVegetarian' is implemented by calling 'identify' on its
argument and switching on the result, which is interpreted as a
'FoodType'. The 'vegetarian' concrete class implements 'eat' by passing
its 'food' argument, 'f' (corrected in the picture above), to
'isVegetable'. If 'f' is a vegetable, 'eat' succeeds.
(Of course the problem is broken because 'eat' has to return a status
in order to know whether the 'vegetarian' ate the 'food'.)

Wow. That's mighty complicated for avoiding a dynamic_cast.
But in principle you are doing just that: figure out if
the passed food object is of type vegetable.

Much simpler to do:

void Vegetarian::eat( food& meal )
{
if( dynamic_cast< Vegetable* >( &meal ) ) {
// proceed, meal is a vegetable or derived from
// it
}
}

Well, if you are going to use dynamic_cast, there is really no point in
having polymorphism.
Never duplicate information the compiler can provide you with!
That also means that having some sort of 'Identify Tag' in a class
isn't a very good idea. The compiler already provides you with one.
Just use that, instead of coming up with your own.

Actually, there is no identifier tag. That is the whole point.

At the moment you return some magical constant from some function
you have some sort of identifying tag.

But fell free to implement with your method:

class Food
{
...
};

class Vegetable : public Food
{
...
};

class Salad : public Vegetable
{
...
};

I really would like to see how you implement
IsVegetable() and
IsSalad()
with your method of using a virtual identify() method
in the base class.

Note: For a Salad object, both IsVegetable() and IsSalad()
have to return true.
a virtual function that returns opaque data. The trick is to use the
utility to get meaningful semantics. For example, 'vegetarian' can use
a whole suite of utility functions like 'isVegetable', 'isOvaLacto',
'isSaltFree' etc. In order for you to get the same behavior, your
'vegetarian' class becomes dependent on the entire 'food' class
hierarchy because you have to do a dynamic_cast to every 'food'
subtype.

But at least it solves the above problem. A Salad object *is* a Vegetable
object, thus the dynamic_cast succeeds.
Yes. The Vegetarian class gets dependent on the food class. And yes, I agree,
usually it is not a good idea. That's why using such identify tags (be it some magical
return value or using a dynamic_cast) should be used wisely. But if those
identify tags are the solution, then at least don't do it by introducing some
magical constants and a function which returns one of them.

That's what it does. /david

Sorry. But a double dispatch is something completely different.
 
K

Karl Heinz Buchegger

Ioannis said:
virtual void eat(food &f) try
{

veg &rveg= dynamic_cast<veg &>(f);

// Process food

There is a potential problem with that.
dynamic_cast throws an exception if you try to cast
a reference and the cast does not succeed. So in this
case casting a pointer (where no exception is thrown)
is really better:

veg* pVeg = dynamic_cast< veg* >( &f );
 
Y

yvinogradov

<The problem is, that now you have some knowledge of Vegetarians
<in the Food class and usually this is not a good idea.

having done some grocery shopping I know for a fact that there is a lot
of food packages (labeled "low fat", "low carb" and alike) out there
targeted for specific food consumers - a clear example as it seems to
me that in the real world the class Food knows a lot about the specific
subclasses of the class Human :)
 
K

Karl Heinz Buchegger

<The problem is, that now you have some knowledge of Vegetarians
<in the Food class and usually this is not a good idea.

having done some grocery shopping I know for a fact that there is a lot
of food packages (labeled "low fat", "low carb" and alike) out there
targeted for specific food consumers - a clear example as it seems to
me that in the real world the class Food knows a lot about the specific
subclasses of the class Human :)

:)

One could it express differently:
The consumer reading the words, knows what "low fat" means to him.
Different consumers give different meaning to those words. So it
is not the food that knows something about itself, but it is the
consumer who knows how to interpret the hints given by the food.

And of course, sometimes the consumer has to ask the clerk.

Which brings us to a 3-rd way of implementing the problem: Install
some Clerk class, who decides if a food can be handled by a customer.
Eg. It does this by asking both classes for their properties and tries
to match them.
 
I

Ioannis Vranos

<The problem is, that now you have some knowledge of Vegetarians
<in the Food class and usually this is not a good idea.

having done some grocery shopping I know for a fact that there is a lot
of food packages (labeled "low fat", "low carb" and alike) out there
targeted for specific food consumers - a clear example as it seems to
me that in the real world the class Food knows a lot about the specific
subclasses of the class Human :)


Actually the above means that Human knows what food types are suitable
for him.
 
I

Ioannis Vranos

Karl said:
There is a potential problem with that.
dynamic_cast throws an exception if you try to cast
a reference and the cast does not succeed. So in this
case casting a pointer (where no exception is thrown)
is really better:

veg* pVeg = dynamic_cast< veg* >( &f );


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

yvinogradov

Change the abstract methond in Food from IsSuitableForVegetarian to
ContainsAnimalProtein (or whatever else the vegetarians don't eat) and
you don't need a clerk, an educated consumer can take it from there and
decide what to do with this food.
 

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