7

My classes are

  • Base
    • Derived_A
    • Derived_B
  • Parent
    • Child_One
    • Child_Two

Base has two signature functions:

virtual void foo( const Parent& ) = 0;
virtual void bar( const Base& ) = 0;

, which other parts of the program expect.

The problem is:

Derived_A treats Child_One and Child_Two the same. But Derived_B treats them differently.

How should I implement this?

One way is to find out what kind of object is passed to Derived_B.foo. This would be apparently "a design flaw". The other way I tried is to change the signature functions as:

class Base
{
  class Derived_A;
  class Derived_B;

//  virtual void bar( const Base& ) = 0;
  virtual void bar( const Derived_A& ) = 0;
  virtual void bar( const Derived_B& ) = 0;
}

class Derived_A: public virtual Base
{ 

  virtual void foo( const Parent& ) = 0;
}

class Derived_B: public virtual Base
{ 
  virtual void foo( const Child_A& ) = 0;
  virtual void foo( const Child_B& ) = 0;
}

But now the bar function cannot use Base.foo. So I have to write the bar function twice, although the code is exactly the same.

Are there any other ways to deal with the problem? which one do you suggest?

P.S. I couldn't think of a good title. Please feel free to modify it.

Eliad
  • 894
  • 6
  • 20
  • You cannot pass children of `Parent` to `foo(Parent)`. You can only pass `Parent` instances. See http://stackoverflow.com/questions/274626/what-is-object-slicing You need references or pointers for polymorphism. – eerorika Apr 16 '15 at 09:33
  • 4
    Look up the visitor design pattern. – Peter Apr 16 '15 at 09:34
  • I actually use references in my program. I tried to simplify it but overdid it. – Eliad Apr 16 '15 at 09:47

4 Answers4

4

The problem you are describing is called Double Dispatch. The link describes the problem and a few possible approaches to a solution (including polymorphic function signatures and the visitor pattern).

utnapistim
  • 26,809
  • 3
  • 46
  • 82
2

You could've easily made a virtual foo method in Parent. Since you want Derive_A to treat all Parent's subclasses the same, why not implement a class that does just that in Parent. That is the most logical thing, since chances are, if you want to do the same to both of them, then both of them must have similar data, which is exist in Parent.

class Parent{
   virtual void treatSame(){
       // Some operations that treat both Child_A, and Child_B
       // the same thing to both Child_A and Child_B.
   }
   virtual void foo() = 0;
}

Since you want Derived_B to do different operations in both Child_A and Child_B, take advantage of polymorphism. Consider the rest of the classes below:

class Child_A : public Parent{
    virtual void foo(){
        // Foo that is designed for special Child_A.
    }
}

class Child_B : public Parent{
    virtual void foo(){
        // Foo that is designed for special Child_B.
    }
}


class Base{
     virtual void foo(Parent) = 0;
     virtual void bar(Base) = 0;
}

class Derived_A: public Base
{ 
  virtual void foo( Parent& p){
     p.treatSame();
  }
}

class Derived_B: public Base
{ 
  virtual void foo( Parent& p){
      p.foo();  // Calls appropriate function, thanks to polymorphism.
  }
}

A possible usage is the following:

int main(){
    Child_A a;
    Child_B b;

    Derived_A da;
    da.foo(a);  // Calls a.treatSame();
    da.foo(b);  // Calls a.treatSame();

    Derived_B db;
    db.foo(a);  // Calls a.foo();
    db.foo(b);  // Calls b.foo();
}

Note that this will only work when the parameters are pointer or reference (I prefer to deal with reference when possible). Virtual dispatch (selecting appropriate function) won't work otherwise.

Joey Andres
  • 106
  • 6
  • `virtual` function in `Parent` is exactly what I would do. `treatSame` could be removed and it's implementation simply put in `Derived_A::foo`, but it's hard to tell which makes better sense in such an abstract example. – eerorika Apr 16 '15 at 09:54
  • you can't put foo in parent Base calls foo and operates on Parent. The operation Base does on Parent inside foo may require access to things Parent.foo can't access. – Olayinka Apr 16 '15 at 09:58
  • @user2079303 But having methods that deal directly with Parent in Derived_A::foo is a code smell, specifically, the coupling is too tight (relies too much on other classes). Methods that are specific to Parent must stay with Parent, otherwise, you got to rethink your design. – Joey Andres Apr 16 '15 at 09:59
  • In your suggestion `Child_A` and `Child_B` **act** the same or differently. I want them to be **treated** differently. I get some information from them. The main operation is done in `Base`'s subclasses. – Eliad Apr 16 '15 at 09:59
  • @Olayinka I'm not catching you. What if Parent is a ```pure virtual``` class? Then, ```foo``` could easily become an interface or abstract class, since you can't instantiate it. – Joey Andres Apr 16 '15 at 10:02
  • @Furihr You can treat the differently by calling foo. See the example in my ```main``` function. Derived_B db calls the appropriate foo. If you pass in an instance of Child_A, Child_A::foo() is called. If you passed in an instance of Child_B, Child_B::foo() is called. – Joey Andres Apr 16 '15 at 10:05
  • Calling Parent.foo inside Base.foo is a bad idea. The OP says Derived_B treats Child_A and Child_B differently. You can't simply send all the operations involved to Parent.foo, what if there are other members and methods involved that are only accessible by Derived_B? – Olayinka Apr 16 '15 at 10:08
  • @JoeyAndres You can't tell if there is more or less coupling in such an abstract example. `Derived_A` is coupled with `Parent` in either case because it does "treat" it in one way or another. However for example, if "treatSame" means "Store the instance in an instance of class C provided by the caller", then implementing `treatSame` in `Parent` would couple both `Parent` and `Derived_A` with `C`. If the implementation is in `Derived_A` then `Parent` can be blissfully ignorant of `C`. As I said, it's hard to tell which makes better sense in such an abstract example. – eerorika Apr 16 '15 at 10:12
  • @Olayinka Again, Parent.foo **don't have to do a single thing.** It could easily virtual dispatch (thanks to **polymorphism**) and call ```Derived_B.foo()``` (if you passed in an instance of Derived_B), which then calls all the functions that exist in Derived_B. *What if there are other members and methods...?* Is a question that is nullified if you know the basics of **POLYMORPHISM**. – Joey Andres Apr 16 '15 at 10:13
  • @user2079303 If I were to do that, I could easily overload the stream operator, or implement a Serializable interface so that I can acquire data for them to be stored in C. Or I could utilize aggregation on Parent, in which I could also do what I said above. There is always a way to minimize coupling (of course, you could never eliminate it). – Joey Andres Apr 16 '15 at 10:18
  • 1
    @JoeyAndres, I know what you are saying, but calling `Child_B::foo()` cannot help when the main operation is done in `Derived_B` and NOT in `Child_B`. `Derived_B` has the information that `Child_B` does not. Again, in your example `Child_A` and `Child_B` act (not treated) differently. – Eliad Apr 16 '15 at 10:21
  • 1
    To make it more clear. In your implementation I call Child_A and she says "hello"; I call Child_B, she says "How do you do?!". They act differently. But I want to take Child_A to school and Child_B to kindergarten. They are treated differently. – Eliad Apr 16 '15 at 10:27
  • @Furihr That's what I've been trying to explain since. Thanks for putting it well. – Olayinka Apr 16 '15 at 10:27
  • @Furihr I'm just wondering what is stopping you from putting those operations that treat each child differently in Derive_B::foo(Child_A&) and Derive_B::foo(Child_B&), in which you can access data where Parent subclasses don't have access?? Btw, you can put foo(Parent&) in Base, that is allowed in the language. – Joey Andres Apr 16 '15 at 11:20
  • @JoeyAndres Because presumably that logic is associated with the calling class. If OP was implementing `Child_A::getTakenToSchoolByParent` and `Child_B::getTakenToKinderByParent`, and calling them from `Base::takeChildToSchool` you'd be saying "no, no, re-think your design". – OJFord Apr 16 '15 at 11:36
2

Without details of what the two type hierarchies' relation is with each other and how they interact, it's impossible to say what approach is appropriate. I've composed an overview of the other answers and another viable alternative that can be extended to the visitor pattern which was mentioned in a comment.

Performing the polymorphic behaviour in the children implementing a virtual function in Parent as already suggested by Joey Andres is quite typical object oriented solution for this problem in general. Whether it's appropriate, depends on the responsibilities of the objects.

The type detection as suggested by Olayinka and already mentioned in your question certainly smells kludgy, but depending on details, can be the minimum of N evils. It can be implemented with member function returning an enum (I guess that's what Olayinka's answer tries to represent) or with a series of dynamic_casts as shown in one of the answers in the question you linked.

A trivial solution could be to overload foo in Base:

struct Base {
    virtual void foo(const Parent&) = 0;
    virtual void foo(const Child_Two&) = 0;
};
struct Derived_A: Base { 
    void foo(const Parent& p) {
        // treat same
    }
    void foo(const Child_Two& p) {
        foo(static_cast<Parent&>(p));
    }
};
struct Derived_A: Base { 
    void foo(const Parent& p) {
        // treat Child_One (and other)
    }
    void foo(const Child_Two& p) {
        // treat Child_Two
    }
};

If there are other subtypes of Base that treat Child_One and Child_Two the same, then the implementation of foo(const Child_Two&) may be put in Base to avoid duplication.

The catch of this approach is that foo must be called with a reference of proper static type. The call will not resolve based on the dynamic type. That may be better or worse for your design. If you need polymorphic behaviour, you can use the visitor pattern which essentially adds virtual dispatch on top of the solution above:

struct Base {
    foo(Parent& p) {
        p.accept(*this);
    }
    virtual void visit(Child_A&) = 0;
    virtual void visit(Child_B&) = 0;
};

struct Parent {
    virtual void accept(Base&) = 0;
};

struct Child_A: Parent {
    void accept(Base& v) {
        v.visit(*this);
    }
};
// Child_B similarly

struct Derived_A: Base { 
    void treat_same(Parent&) {
        // ...
    }
    void visit(Child_A& a) {
        treat_same(a);
    }
    void visit(Child_B& b) {
        treat_same(b);
    }
};
struct Derived_B: Base { 
    void visit(Child_A&) {
        // ...
    }
    void visit(Child_B&) {
        // ...
    }
};

There's a bit more boilerplate, but since you seem very averse to implementing the behaviour in the children, this may be good approach for you.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
1

I'm not sure of the syntax but you get the gist.

class Base{
  virtual void bar( Base ) = 0;
  virtual void foo( Parent ) = 0;
}

class Derived_A: public virtual Base{ 
  virtual void foo( Parent ) = 0;
}

class Derived_B: public virtual Base{ 
  virtual void foo( Parent ){
      //switch case also works
      return parent.get_type() == Parent::TYPE_A ? foo_A((Child_A)parent) : foo_B((Child_B)parent);
  }
  virtual void foo_A( Child_A ) = 0;
  virtual void foo_B( Child_B ) = 0;
}

class Parent{
  virtual int get_type() = 0;
}

class Child_A: public virtual Parent{ 
     return Parent::TYPE_A;
}

class Child_B: public virtual Parent{ 
     return Parent::TYPE_B;
}
Olayinka
  • 2,813
  • 2
  • 25
  • 43
  • This is the first approach. I could use it but apparently this is a design flaw. – Eliad Apr 16 '15 at 10:13
  • @Furihr How is this a design flaw? – Olayinka Apr 16 '15 at 10:26
  • I cannot find where (on stackoverflow) I read it now. But, It said something like "If your program needs to find out the type of an object and act accordingly, it is a design flaw." Since it was someone with 28.6k or 26.8(?) reputation. I thought s/he knew what s/he where talking about. – Eliad Apr 16 '15 at 10:34
  • @Furihr I totally agree with you but in this case I believe that the design is flawed only because the problem is. From your comment in the other answer. Before deciding who to take to school or kindergarten you must know which one of them it is individually. The other solution is adding virtual methods to Parent such that these methods are called specifically when Derived_B calls its foo. But this is also flawed. – Olayinka Apr 16 '15 at 11:21