10

I recently saw an OO design question on some forum and started thinking of using RTTI. However this must be bad design but I am unable to think of an alternative. Here is the simple question :

Create a C++ program for the following scenario using OO concepts -

My dog, named Buddy, lives in the backyard. He barks at night when he sees a cat or a squirrel that has come to visit. If he sees a frog, and he is hungry, he eats it. If he sees a frog and he isn't hungry, he plays with it. If he has eaten 2 frogs already, and is still hungry, he will let it go. If he sees a coyote, he crys for help. Sometime his friend Spot stops by, and they chase each other. If he sees any other animal, he simply watches it. I would expect that you would have an animal class, and a cat, dog, squirrel, coyote class that inherits from the animal class.

I started thinking of having a see() method in the dog class which takes an Animal argument and then checks the actual type of the object (frog, cat etc) and takes the required action - play, chase etc depending on the actual type. However this would require RTTI which must be bad design. Can anybody please suggest a better design which would avoid RTTI and also point out the mistake in my thinking?

Mahesh
  • 34,573
  • 20
  • 89
  • 115
vjain27
  • 3,514
  • 9
  • 41
  • 60
  • 1
    I think this question belongs on programmers.stackexchange.com. – Onorio Catenacci Feb 15 '12 at 03:22
  • 1
    A **very** important note is RTTI, as handled *by the compiler*, is not the only way for an object to identify itself. You may also have an `enum AnimalTypes` and `virtual Animal::GetType()` method, or a `Uuid Unknown::GetClass()` method (a la COM's `IUnknown::QueryInterface`) or even a problem-specific `Animal::IsScarierThan(Animal * me)`. There are a number of ways to use object-oriented design to get runtime type information, without using RTTI as such. – ssube Feb 15 '12 at 04:29
  • 3
    *that has come to visit* --> you mean that the cat and squirrel are *visitors* ;) ? – Matthieu M. Feb 15 '12 at 07:27

4 Answers4

13

There are a ridiculously large number of ways to satisfy this problem using "OO concepts," depending on what you want to emphasize.

Here's the simplest solution that I can come up with:

class Animal {
public:
    virtual void seenBy(Buddy&) = 0;
};

class Buddy {
public:
    void see(Cat&)      { /* ... */ }
    void see(Squirrel&) { /* ... */ }
    // ...
};

class Cat : public Animal {
public:
    virtual seenBy(Buddy& b) { b.see(*this); }
};

class Squirrel : public Animal {
public:
    virtual seenBy(Buddy& b) { b.see(*this); }
};

// classes for Frog, Coyote, Spot...

If you need multiple kinds of "perceiving" animals, it's straightforward to make a virtual wrapper for see (producing a form of double dispatch):

// On a parent class
virtual void see(Animal&) = 0;

// On Buddy
virtual void see(Animal& a) { a.seenBy(*this); }

The above requires that the Animal class know something about the Buddy class. If you don't like your methods being passive verbs and want to decouple Animal from Buddy, you can use the visitor pattern:

class Animal {
public:
    virtual void visit(Visitor&) = 0;
};

class Cat : public Animal {
public:
    virtual void visit(Visitor& v) { v.visit(*this); }
};

class Squirrel : public Animal {
public:
    virtual void visit(Visitor& v) { v.visit(*this); }
};

// classes for Frog, Coyote, Spot...

class Visitor {
public:
    virtual void visit(Cat&) = 0;
    virtual void visit(Squirrel&) = 0;
    // ...
};

class BuddyVision : public Visitor {
public:
    virtual void visit(Cat&)      { /* ... */ }
    virtual void visit(Squirrel&) { /* ... */ }
    // ...
};

class Buddy {
public:
    void see(Animal& a) {
        BuddyVision visitor;
        a.visit(visitor);
    }
};

The second mechanism could be used for purposes other than Buddy seeing an animal (possibly for that animal seeing Buddy). It is, however, more complicated.


Note that OO is definitely not the only way to solve this problem. Other solutions exist that may be more practical for this problem, such as storing the properties of the various animals that cause Buddy to bark, eat, play, etc. This additionally decouples the Buddy class from the Animal class (even the visitor pattern needs an exhaustive list of everything that Buddy can perceive).

John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
  • Hint: `BuddyVision()` cannot be bound to a non-const reference. You should either provide an overload of `visit` or instantiate it on the stack as a named argument (yes, this is stupid...). – Matthieu M. Feb 15 '12 at 07:27
6

The design specifically calls for recognizing certain entities in order to perform certain operations on them. Because there is no rhyme or reason with regard to why certain operations go with certain entities (ie: it's all arbitrary), what you're looking at is either type-based dispatch or property-based dispatch. I'd go with the latter.

Give each entity some set of properties. The dog would thus react based on those properties. Cat and Squirrel would have the property, "Dog should Bark at me." When the Dog encounters an entity with such a property, it would perform the appropriate action.

In this case, an entity is nothing more than the sum of its properties as well as the behaviors based on encountering other entities with various properties. The entity may also have some state associated with it. There would not be a specific Dog or Cat class. There would just be an entity with Cat-like properties and behaviors, and an entity with Dog-like properties and behaviors.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • This is definitely a good idea. It's certainly an illustration of why OO is not the end-all be-all of programming paradigms. However, it's probably also worth learning how "true" OO would solve the problem, as a point of comparison. Plus, the question is explicitly an exercise in OO design. (This assumes you define OO as taking advantage of polymorphism, not merely as an association of data and functions that operate on that data.) – John Calsbeek Feb 15 '12 at 03:34
  • @JohnCalsbeek I fail to see how this is somehow "not OO". Even if I were to implement a property-based entity definition system, I'd use an OO design to accomplish it. What on earth constitutes "true OO" in this or any other case?? I've even finished my degree, and I see nothing here that falls outside of what can be accomplished using OOP. – TheBuzzSaw Feb 15 '12 at 03:49
  • @TheBuzzSaw: Not to speak for John, but personally, I don't consider a design "object oriented" unless it involves polymorphism via inheritance. Without that, it's nothing that you couldn't easily do in C. – Nicol Bolas Feb 15 '12 at 03:57
  • Yes. There's a distinction between "an OO design" and "a design that is most naturally implemented with OO." OO tools are a big hammer, and this definitely looks like a nail. – John Calsbeek Feb 15 '12 at 04:06
  • @NicolBolas: I respectfully disagree. The fact that the dog reacts to squirrels is particular to *this* dog, not the to squirrels. As far as dependencies go, this is horrendous. – Matthieu M. Feb 15 '12 at 07:25
  • @MatthieuM.: The given design doesn't explain how other dogs react to those things. Further, there's nothing in my suggestion that requires that the properties and behaviors are general to all dogs. It's a component system, not an hierarchical system. – Nicol Bolas Feb 15 '12 at 07:37
3

Hint: use virtual functions (on the target animals) instead of RTTI.

Neil G
  • 32,138
  • 39
  • 156
  • 257
  • And virtual function mechanism does not use RTTI? o.O – lapk Feb 15 '12 at 03:16
  • 1
    Actually how would you do this? The animals shouldn't know what to do when they are seen by a dog should they? I would think the dog would need to identify the animal and do what it wanted based on what it perceived the animal as (a `switch` in terms of code). I would actually go with RTTI in this case. – Seth Carnegie Feb 15 '12 at 03:18
  • 2
    @AzzA nope, it uses dynamic dispatch, not RTTI – Seth Carnegie Feb 15 '12 at 03:18
  • 2
    If this problem is entirely focused on the dog named Buddy, then the animal classes have no other purpose except to inform Buddy what to do about them, and the problem is probably best formulated as a virtual function on the Animal class. If there are multiple "perceiving" dogs, then the problem becomes one of double dispatch, which *can* have other implementations, but in C++ is probably best suited by a see() virtual that calls an overloaded seenBy() virtual. (The first type can still perform all the necessary work, if seenBy() calls back to it.) – John Calsbeek Feb 15 '12 at 03:25
  • @JohnCalsbeek it kinda doesn't make sense for an animal to know what to do _when it is seen by another animal_, for example, when a bear sees a fish (whether I know it or not) the fish isn't encoded with "get eaten", it's the bear that is encoded with "eat" – Seth Carnegie Feb 15 '12 at 03:29
  • An alternative that allows the Animal subclasses to be independent of Buddy is the Visitor pattern. – John Calsbeek Feb 15 '12 at 03:29
  • 1
    @SethCarnegie Indeed, that doesn't make sense if you consider the Animal subclasses to be representations (simulations, even) of real-world objects. However, you may as well think of them as representations *of Buddy's perception of those animals*, which is all this problem requires. May as well solve only the problem at hand and get an easy-to-understand implementation in the process. – John Calsbeek Feb 15 '12 at 03:31
  • @JohnCalsbeek then it would make sense to have a base class `BuddyPerception` from which they would all derive, and then have the function accept a `BuddyPerception&` instead of an `Animal`, no? – Seth Carnegie Feb 15 '12 at 03:32
  • @SethCarnegie Certainly. But in a program that intends to simulate the behavior of some agent, virtually every object in the program will be a "perception" of some sort. You'd end up with `AnimalPerception`, `FoodPerception`, `EmotionPerception`, etc. If you define your domain carefully, you can keep the names simple and still accurate. However, naming challenges are probably beyond the scope of this question. – John Calsbeek Feb 15 '12 at 03:37
  • @SethCarnegie I thought, RTTI information was stored in `vtable` for polymorphic classes. No? – lapk Feb 15 '12 at 05:00
  • I am really confused. Could anybody please clarify this 'perception' concept by example ? – vjain27 Feb 15 '12 at 06:18
  • 1
    @vjain27 It's really just about what to name your classes. In real life, the squirrel itself has no control over what Buddy does when he sees the squirrel. By analogy, the `Squirrel` class shouldn't ever have to refer to the `Buddy` class. *Unless* the `Squirrel` class represents Buddy's thoughts about a squirrel, not the behaviors of the squirrel itself. – John Calsbeek Feb 15 '12 at 08:17
  • @JohnCalsbeek: I am not getting the difference between the two cases you mentioned in your comment - 1. having one virtual functions in Animal class and 2. having two virtual functions - see and seenBy. I can understand the second one from the example you have already provided. Could you please explain the first one ? – vjain27 Feb 15 '12 at 17:50
  • @vjain27 In the example I provided, you'll note that none of the `see` methods are virtual. If you make them virtual, add them to `Animal`, make `Buddy` inherit from `Animal`, and change the parameter of `seenBy` to `Animal&`, you have a setup in which any animal can see any other animal. – John Calsbeek Feb 15 '12 at 19:33
0

Most of the time you can replace RTTI by messaging.

Sort of

Id id = object->send(WHO_ARE_YOU); 
switch(id)
{
  case ID_FROG: ...; break;
  case ID_CAT: ...; break;
}

Messaging is more flexible than RTTI in principle:

other_object->send(IS_SCARRY_OF, this); 

as it allows to design relationships that are unknown at the moment. Say tomorrow your dog will see racoon that is defined in some other DLL and yet in Pascal.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • 1
    That's nothing more than non-compiler-based RTTI. It's the same concept: objects have an identifier that determine their type at runtime. – Nicol Bolas Feb 15 '12 at 03:55
  • @Nicol Bolas - not exactly, see text starting from "Messaging is more flexible..." RTTI describes only one "message" - WHO_ARE_YOU per se. – c-smile Feb 15 '12 at 04:00
  • 1
    Or you could just have a function called "is_scarry_of" that takes an object. All you're doing is using message passing to take the place of virtual dispatch and RTTI. And therefore, losing the language features and guarantees of both. – Nicol Bolas Feb 15 '12 at 04:04