5

This is my first post, so be kind. This is an interview question I recently got, but I couldn't find an answer after searching (google , C++FAQ etc).

There is an interface I1 with a behavior b1(). There are 3 classes A,B,C. All these classes are implementing the interface I1 by overriding b1(). There is a fourth class D which has behavior (b1) defined in interface I1 and an extra behavior b2

Question is how do you design the class D.

My answer was create another Interface I2 which defines behavior b2() and make class D implement both I1 and I2 (multiple Inheritance in C++) by overriding both b1() and b2()

Interviewer agreed to the solution, but asked what if in future new classes with new set of behaviors come up, how will we handle that

I could only think of adding more Interfaces (I3, I4 etc) and doing multiple inheritance, but I am aware that here you end up in a huge number of derived classes with corresponding Interfaces

The interviewer seemed to be expecting a better solution but he didn't reveal the answer. I would love to know how the experts here would solve this design problem.

PS: After serious thought on this I think the answer may lie in using a design pattern, but looking at common design patterns I could not find any that matched this problem

edit 1: I have more questions and clarifications, so editing the post here, not sure if this is right way or I need to post this as an answer to my own question.

First Let me thank @Nawaz, @Alexandre and @Sjoerd for your valuable inputs. I am just starting to learn the design aspects in C++/design patterns, so pardon my ignorance on the subject.

The example of Vistor pattern by @Nawaz was really helpful, but I guess it is only a particular case of the original problem asked by the interviewer. @Alexandre has correctly pointed out the scenarios here.

Let me explain another aspect of this. When we talk of behaviors ,we need to group them based on

1) common behaviors related to a group of objects or object. (This is intuitive or can be observed as in the real world) eg: behaviors of a Dude (taking the example of @Nawaz) - i,e walking, eating, studying etc

2) uncommon or very peculiar behaviors related to a group (This is counter intuitive) eg: just for argument sake, consider a Dude who composes music (I know this example is not perfect)

3) totally unrelated behavior to the group. I cannot think of an example, but my point is lets say for some wierd reason we need to give the object this behavior.

So I think Visitor pattern can solve the problem in 1) but I suspect it wont for 2) and 3).

taking the IDude example, we would need the following changes to make a Dude who can compose music.

    class IComposerBehavior;

    class IComposer
    {
       public:
          virtual ~IComposer() {}
          virtual void accept(IComposerBehavior *behaviour) = 0 ;
    };

    class IComposerBehavior
    {
       public:
          virtual ~IComposerBehavior() {}
          virtual void visit(IComposer * ) = 0;
    };

    class Dude : public IDude, public IComposer
    {
        public:
          virtual void accept(IDudeBehavior *behaviour)
          {
              behaviour->visit(this);
          }
          virtual void accept(IComposerBehavior *behaviour)
          {
              behaviour->visit(this);
          }
    };

    class SymphonyComposerBehavior : public IComposerBehavior
    {
      public:
         virtual void visit(IComposer *dude) { cout << "Dude is composing a symphony" << endl;   }
    };

Similarly we need to change client code also to account for the SymphonyComposerBehavior.

So basically we ended up changing both the Dude class code and client code, which negates effect of the pattern.

I think the interviewer was asking about new behavior which cannot be put in a group of related behaviors which were previously identified. So in this case even if the classes are fixed can a visitor pattern solve as @Alexandre pointed out?

Let me give an example here just of the top of my head (not sure if this is a correct example to represent the problem). Lets say I need to design an application for a Robot manufacturing firm. The requirement gradually grows as

- Initially We are only producing Toy Robots
- Then Human helper Robots
- Then Self Healing Robots (would just correct itself when defective)
- Then Humanoid Robots
- Then machine Robots (that are not human like but as a substitute for any machine you can think of) . I have deliberately put this here even though its place should be before with a correct evolution scheme.
- finally Humanoid Robots with life (atleast we can dream :-) )

So if we know the complete list of Robots prior to designing the application, we could come with a better design, but how do we design when each new type is introduced sequentially in the above order. My point here is we know that a Robot has certain behaviors or characteristics, but when uncommon features have to be introduced later ( like self healing, machine robot) how do we go about?

Thanks.

John PMK
  • 53
  • 1
  • 4
  • What does the interviewer want? Of course are new behaviour and new classes coupled, what else? New behaviour without new classes? New classes without new behaviour? He's not clear at all about what he wants to hear. The current answers below talk about "Visitor", but that requires new visitors (read: new classes) as well. – Sjoerd Aug 06 '11 at 13:12
  • @Sjoerd: Visitor is a kind of last resort solution when the classes in the hierarchies are fixed and the number of behaviors is expected to grow. I'm afraid the question has no *generic* answer. – Alexandre C. Aug 06 '11 at 13:15
  • @Sjoerd, the question as I understand, was if we are adding new behavior that is unrelated to behaviors in an existing Interface and if a new class or existing class needs to get both these behaviors how do we go about? – John PMK Aug 07 '11 at 04:31

2 Answers2

7

I think the interviewer was expecting you to talk about Visitor Pattern.

Yes, visitor pattern lets you add new behavior(s) to existing structure of classes, without any further addition/derivation of class/interface to the structure. All that it requires you to implement the behavior only and the visitor pattern lets you add this behaviour to the structure of the classes.

Read this wiki entry; it explains the pattern:

Here is one simple implementation of visitor pattern:

class IDudeBehavior;

class IDude
{
   public:
      virtual ~IDude() {}
      virtual void accept(IDudeBehavior *behaviour) = 0 ;
};

class IDudeBehavior
{
   public:
      virtual ~IDudeBehavior() {}
      virtual void visit(IDude * ) = 0;
};

class Dude : public IDude
{
    public:
      virtual void accept(IDudeBehavior *behaviour)
      {
          behaviour->visit(this);
      }
};

class LaughDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Laughing" << endl; }
};

class WalkDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Walking" << endl; }
};
int main() {
        IDude *dude = new Dude();
        dude->accept(new LaughDudeBehavior());
        dude->accept(new WalkDudeBehavior());
        return 0;
}

Online demo : http://ideone.com/Kqqdt

As of now, the class Dude has only two behaviors, namely LaughDudeBehavior and WalkDudeBehavior but since its a visitor pattern you can add any number of behavior to the Dude, without editing the class Dude. For example, if you want to add EatDudeBehavior and StudyCplusCplusDudeBehavior, then all you need to implement IDudeBehavior as:

class EatDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Eating" << endl; }
};

class StudyCplusCplusDudeBehavior : public IDudeBehavior
{
  public:
     virtual void visit(IDude *dude) { cout << "Dude is Studying C++" << endl; }
};

And then you need to accept these behavior as:

dude->accept(new EatDudeBehavior ()); 
dude->accept(new StudyCplusCplusDudeBehavior ());

Demo after adding these new behaviors : http://ideone.com/9jdEv


Avoid Memory Leak

There is one problem with the above code. Everything looks fine, except that it leaks memory. The program creates many instances of classes using new, but it never deallocated them using delete. So you need to think about this as well.

Memory leak can be fixed very easily as:

int main() {
        IDude *dude = new Dude();

        std::vector<IDudeBehavior*>  behaviours;
        behaviours.push_back(new LaughDudeBehavior());
        behaviours.push_back(new WalkDudeBehavior());
        behaviours.push_back(new EatDudeBehavior());
        behaviours.push_back(new StudyCplusCplusDudeBehavior());

        for(size_t i = 0 ; i < behaviours.size() ; i++ )
           dude->accept(behaviours[i]);

        //deallcation of memory!
        for(size_t i = 0 ; i < behaviours.size() ; i++ )
           delete behaviours[i];
        delete dude;
        return 0;
}

No memory leak now. :-)

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    Why not use references in accept, still gives you the polymorphic behaviour and means you don't have to manage your heap-allocated temporaries, could just dude->accept(EatDudeBehavior ()); – Andy Dent Aug 06 '11 at 10:13
  • @Andy : Because you wouldn't be able to write `dude->accept(EatDudeBehavior());` if the parameter of `accept` is `IDudeBehavior &`. Making it `const` reference would solve the problem, but it might not be desirable to have `const` instance of visitor for real problems, if visitor maintains and updates some state(s) while visiting objects in the structure. – Nawaz Aug 06 '11 at 13:19
  • @Nawaz, I have edited the post and asked few more sub questions. I am still not sure if Visitor pattern would solve the case of a "Dude who composes music" if for arguments sake we say composing music is very uncommon for Dudes. Thanks for your help – John PMK Aug 06 '11 at 18:09
  • @John: I read the edit in your post. You seem to think that visitor pattern cannot solve the scenario 2 and 3. You're wrong. It can solve them as well. Just look at the definition of `accept(IComposerBehavior*)` function in `Dude` class. Its exactly same as `accept(IDudeBehavior*)`. No difference at all. Both function has exactly same body : `behaviour->accept(this)`. So don't you think there is duplication, and so you need to remove one of the `accept` function? [contd] – Nawaz Aug 06 '11 at 18:56
  • @John : [contd].. What you're doing is exactly the problem visitor pattern solves. You're editing the class, just to add behavior - which is wrong, because visitor pattern adds behavior by defining yet another class deriving from `IDudeBehaviour` interface. You should implement `class RockMusicComposeDudeBehavior : public IDudeBehavior {};`. – Nawaz Aug 06 '11 at 19:02
  • @Nawaz, thanks for your inputs again. I agree to your solution where RockMusicComposerDudeBehavior : public IDudeBehavior {}; , yes if you make Composing music a common type of Dude behavior, Visitor pattern will solve the issue, infact that's why in my post I pointed out case 1) . What I am not clear is - lets say for arguments sake (this is since I cannot think of a uncommon behavior of Dude) we say Composing music cannot be derived from IDudeBehavior and its should be branched as a seperate behavior tree which is more common to Musician. This is the example code I gave in my post [contd] – John PMK Aug 07 '11 at 04:21
  • @Nawaz, [contd] So do you still think if given the fact that IComposerBehavior is in itself a seperate tree of behaviors unrelated to IDudeBehavior, and if we need to provide this behavior to our Dude class (for a reason), Visitor can solve the problem? Thanks – John PMK Aug 07 '11 at 04:24
  • @John: That is still a visitor pattern. But that is wrong way of applying. No need for other interface. Its just useless. Unless you tell me the real problem with `IDudeBehavior`, there is no need to create yet another interface. Common behavior or uncommon behavior is not an argument to support what you think. – Nawaz Aug 07 '11 at 04:43
  • @Nawaz, thanks for your reply. may be I could not express myself clearly. Let me give one last example. I am creating a virtual reality app which has Dudes. But then for some reason I need a peculiar Dude who is immortal, so i need the behavior called IImmortalBehavior, now it is counter intuitive to make IImmortalBehavior derive from IDudeBehavior. How will we design this? Thanks – John PMK Aug 07 '11 at 15:00
  • @John: Immortality says nothing about the problem you face with `IDudeBehavior` interface. Until you specify the problem clearly, its not possible to solve them. Anyway, at this point, I think you need to write few codes and play with this pattern; hopefully you'll solve that problem (whatever it is), yourself. :-) – Nawaz Aug 07 '11 at 15:15
  • @Nawaz, the problem as I see it is - I can use the Visitor pattern for the Dude class in the virtual reality app (using same code you posted). But when I need to create a ImmortalDude class or a Dude class who has the behavior of being Immortal, how will I design this? one option is if I derive ImmortalDudeBehavior from IDudeBehavior, here Visitor pattern will solve the issue. What I am looking is a way in which I cannot derive ImmortalBehavior from IDudeBehavior (as this is counter intuitive), but at the same time give this behavior to Dude class. I hope I am clear about the problem. thanks – John PMK Aug 07 '11 at 15:56
  • @John: Counter-intuitive for whom? Are the users going to see your code? No. So its you who is writing the code, and nothing is counter-intuitive for you, as you're the designer of the game which in turn requires to you to design the classes in that way to solve the problem elegantly. The word counter-intuitive should apply from user point of view, not from programmer point of view. To a programmer, all that matters is a good design.[contd] – Nawaz Aug 07 '11 at 16:03
  • @John: [contd]. Also, the counter-intuitive is a not real problem, after all its virtual reality where a dude will jump from 1000 floor building, then are you going to add yet another interface `IJump1000FloorBuidlingDudeBehaviour`? If you think, immortality is counter-intuitive, then it will be counter-intuitive no matter what you do. – Nawaz Aug 07 '11 at 16:07
  • @Nawaz, I perfectly understand what you are saying and yes I will go with Visitor pattern in the above example if that would help me avoid the overhead of changing Dude class code and client code (even at the expense of being counter intuitive). What I am trying to say here is only for arguments sake - lets say there is one weirdo guy who wants to design in such a way (as I explained earlier) without ComposerBehavior deriving from IDudebehavior then looks like we do not have any available design pattern or methodolgy to help here or is there any? – John PMK Aug 07 '11 at 17:40
  • @John: I'm not that weirdo, so I cannot think how a weirdo guy thinks. Hence I cannot help ou with a weirdo design of classes :P – Nawaz Aug 07 '11 at 17:46
3

(...) in future new classes with new set of behaviors come up

There are two different things here. New classes, and new set of behaviors. Let us consider each in turn.


If no new classes are to be added, this means that you have a fixed set of classes, and a truckload of behaviors: this makes a potential candidate for the Visitor pattern. The goal of the Visitor pattern is to turn a behavior into a class, and allow it to pattern match (without casting) on the class of the hierarchy it acts upon.

However, Visitor is cumbersome to implement, and if the hierarchy has very simple structure (ie. only two main branches you want to distinguish), you are better with implementing behaviors as free functions and use dynamic_cast to select which branch of the hierarchy the object acted upon belongs to. See there for a justified use case of dynamic_cast.

The real advantage of using Visitor (or the simple dynamic_cast dispatch when applicable) is that the complete code pertaining to a behavior is maintained in only one place. This is not the case with interfaces, where each implementation of an interface is likely to be scattered throughout the various implementation files.


Now, if a bunch of new classes have to be added, and the set of behaviors is fixed, then using and abusing interfaces is the right way to go. Interfaces can be incredibly useful to abstract behavior. However, as soon as the number of behaviors increase, it is cumbersome to maintain since the code gets cluttered in the various classes implementation files.

See also the Template Method pattern, which can apply here.

Please also see this question about the incredible usefulness of interfaces.


What about if the number of classes and the number of behaviors increase ? I'm afraid there is no good problem independent solution. Your only bet here is to abstract smartly, so that the different behaviors do not have to care about what class they act upon.

Consider the following example. You have n container classes (vector, list, deque, set, etc), and m algorithms (find_if, count, copy, for_each, etc).

You can't implement each algorithm in each container class: this would mean you have to write O(nm) code. The solution retained by the standard library (it goes older than that [citation needed]) is to abstract the traversal of the structures: every container class exposes a pair of iterators, and algorithms act on pairs of iterators. This allows one to write O(n + m) code.


In conclusion, in the presence of increasing number of state classes and increasing number of behaviors, you have to find the abstraction which makes the behaviors truly independent on the state classes. There is no design pattern for this: you must use your brain.

In the presence of a fixed number of state classes and increasing number of behaviors, either abstract correctly, or in last resort, use Visitor.

In the presence of an increasing number of state classes and fixed number of behaviors, use interfaces: that's what they are for.

Community
  • 1
  • 1
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • @Alexandre I have edited the post with few more sub questions. Really appreciate your help. As explained in the edited post, will Visitor pattern solve a case where the number of classes are fixed , but uncommon new behaviors come up? Also do you have a small example to show the alternate way of using free functions and dynamic_cast as you suggested? – John PMK Aug 06 '11 at 18:14