Chiel
Chiel

Reputation: 6194

How to design a flexible C++ program in which derived classes interact?

I would like the experts here to have an opinion on the following problem. I would like to design a program in which derived classes from different base classed interact with each other. To make my point clear, I have constructed an example.

Imagine that I try to make a zoo. In this zoo we have animals, and we have fruits, which are my base classes. Only during runtime we know which animals we have, and which fruits. Assume that I have a monkey and a banana.

How do I let this monkey consume a banana in an elegant way, where eating a banana is a very unique skill of this monkey only? Note that I don't want a generic eat function, because I need in my design total freedom in the actions that a derived animal can perform.

This is the accompanying example:

#include <string>
#include <iostream>

class cfruit
{
};

class cbanana : public cfruit
{
};

class canimal
{
};

class cmonkey : public canimal
{
  public:
    int eatbanana(cbanana *) { std::cout << "BURP" << std::endl; }
};

int main()
{
  cfruit  *fruit;
  canimal *animal;

  // on runtime the animal and the fruit is decided, so we have to 
  // initialize it on the base pointer

  // assume we get a monkey and a banana
  fruit  = new cbanana();
  animal = new cmonkey();

  // now, we would like our monkey to eat a banana, which we cannot do...
  // UNLESS... we do something really UGLY
  static_cast<cmonkey *>(animal)->eatbanana(static_cast<cbanana *>(fruit));

  return 0;
}

Upvotes: 2

Views: 398

Answers (1)

Stephane Rolland
Stephane Rolland

Reputation: 39926

You are asking us a way to violate the Liskov Principle "elegantly".

When you manipulate a mother class, you should not assume of the concrete derived class that is instanciated behind.

This is called bad design, coding smell.

If your class is indeed a monkey, and you want it to eat bananas: you should not manipulate it as an animal, but as a monkey.

A track to find a solution anyway could start this way: As you explain, there is an implicit link between animals and their fruit. You could create a class Meal which goal is to manage this link explicitely.

struct Meal
{
    virtual void consume() = 0;
}

Meal* factoryCookMonkeyMeal(Monkey& monkey, Banana& banana)
{
    return new MealMonkey(monkey,banana);    
}

struct MealMonkey : public Meal
{
    Monkey& monkey; 
    Banana& banana;

    virtual void consume(){ monkey.eat_banana(banana);};
}

Meal* factoryCookBirdMeal(Bird& bird, Cherry& cherry)
{
    return new MealBird(bird,cherry);
}

struct MealBird : public Meal
{
    Bird& bird;
    Cherry& cherry;

    virtual void consume(){  bird.eat_cherry(cherry);};
}

However you don't tell us precisely the way you determine the animal and the fruit... so that doesn't help... because it is this choice that knows the info that must be used for calling the good factoryMethod that creates the appropriate meal.

Upvotes: 5

Related Questions