views:

100

answers:

2

I feel like the answer to this question is really simple, but I really am having trouble finding it. So here goes:

Suppose you have the following classes:

class Base;
class Child : public Base;

class Displayer
{
public:
    Displayer(Base* element);
    Displayer(Child* element);
}

Additionally, I have a Base* object which might point to either an instance of the class Base or an instance of the class Child.

Now I want to create a Displayer based on the element pointed to by object, however, I want to pick the right version of the constructor. As I currently have it, this would accomplish just that (I am being a bit fuzzy with my C++ here, but I think this the clearest way)

object->createDisplayer();

virtual void Base::createDisplayer()
{
     new Displayer(this);
}

virtual void Child::createDisplayer()
{
     new Displayer(this);
}

This works, however, there is a problem with this:

Base and Child are part of the application system, while Displayer is part of the GUI system. I want to build the GUI system independently of the Application system, so that it is easy to replace the GUI. This means that Base and Child should not know about Displayer. However, I do not know how I can achieve this without letting the Application classes know about the GUI.

Am I missing something very obvious or am I trying something that is not possible?

Edit: I missed a part of the problem in my original question. This is all happening quite deep in the GUI code, providing functionality that is unique to this one GUI. This means that I want the Base and Child classes not to know about the call at all - not just hide from them to what the call is

A: 

Have the application set a factory interface on the system code. Here's a hacked up way to do this. Obviously, apply this changes to your own preferences and coding standards. In some places, I'm inlining the functions in the class declaration - only for brevity.

// PLATFORM CODE
// platformcode.h - BEGIN
class IDisplayer;
class IDisplayFactory
{
     virtual IDisplayer* CreateDisplayer(Base* pBase) = 0;
     virtual IDisplayer* CreateDisplayer(Child* pBase) = 0;
};

namespace SystemDisplayerFactory
{
    static IDisplayFactory* s_pFactory;
    SetFactory(IDisplayFactory* pFactory)
    {
        s_pFactory = pFactory;
    }

    IDisplayFactory* GetFactory()
    {
        return s_pFactory;
    }
};
// platformcode.h - end

// Base.cpp and Child.cpp implement the "CreateDisplayer" methods as follows

void Base::CreateDisplayer()
{
  IDisplayer* pDisplayer = SystemDisplayerFactory::GetFactory()->CreateDisplayer(this);
}


void Child::CreateDisplayer()
{
  IDisplayer* pDisplayer = SystemDisplayerFactory::GetFactory()->CreateDisplayer(this);
}

// In your application code, do this:

#include "platformcode.h"

    class CDiplayerFactory : public IDisplayerFactory
    {
        IDisplayer* CreateDisplayer(Base* pBase)
        {
             return new Displayer(pBase);
        }

        IDisplayer* CreateDisplayer(Child* pChild)
        {
            return new Displayer(pChild);
        }
    }

Then somewhere early in app initialization (main or WinMain), say the following:

CDisplayerFactory* pFactory = new CDisplayerFactory();
SystemDisplayFactory::SetFactory(pFactory);

This will keep your platform code from having to know the messy details of what a "displayer" is, and you can implement mock versions of IDisplayer later to test Base and Child independently of the rendering system.

Also, IDisplayer (methods not shown) becomes an interface declaration exposed by the platform code. Your implementation of "Displayer" is a class (in your app code) that inherits from IDisplayer.

selbie
This does solve the question I posed, but there is a bit of a problem: this all happening deep in the GUI code, providing functionality that other GUIs might not provide at all...
Jasper
I may have gotten your platform objects confused with your app objects. But it doesn't matter. The key point is that interfaces provide a logical abstraction between components that shouldn't have to know about each other. For the other gui that "might not provide at all" as you put it, you're still likely to have to shim out an abstraction to make it compatible with the rest of the code. Identify the commonality with another gui and write an abstraction layer using common interfaces.
selbie
+3  A: 

It seems a classic scenario for double dispatch. The only way to avoid the double dispatch is switching over types (if( typeid(*object) == typeid(base) ) ...) which you should avoid.

What you can do is to make the callback mechanism generic, so that the application doesn't have to know of the GUI:

class app_callback {
  public:
    // sprinkle const where appropriate...
    virtual void call(base&)    = 0;
    virtual void call(derived&) = 0;
};

class Base {
  public:
    virtual void call_me_back(app_callback& cb) {cb.call(*this);}
};
class Child : public Base {
  public:
    virtual void call_me_back(app_callback& cb) {cb.call(*this);}
};

You could then use this machinery like this:

class display_callback : public app_callback {
  public:
    // sprinkle const where appropriate...
    virtual void call(base&    obj) { displayer = new Displayer(obj); }
    virtual void call(derived& obj) { displayer = new Displayer(obj); }

    Displayer* displayer;
};

Displayer* create_displayer(Base& obj)
{
  display_callback dcb;
  obj.call_me_back(dcb);
  return dcb.displayer;
}

You will have to have one app_callback::call() function for each class in the hierarchy and you will have to add one to each callback every time you add a class to the hierarchy.
Since in your case calling with just a base& is possible, too, the compiler won't throw an error when you forget to overload one of these functions in a callback class. It will simply call the one taking a base&. That's bad.

If you want, you could move the identical code of call_me_back() for each class into a privately inherited class template using the CRTP. But if you just have half a dozen classes it doesn't really add all that much clarity and it requires readers to understand the CRTP.

sbi
While your code is a solution to my question, it is not quite what I am looking for... The problem is that the `object->createDisplayer();` is deep in the GUI code, providing functionality that is own to this GUI - meaning that I don't want the game to have this callback at all if possible...
Jasper
@Jasper: I don't think you can avoid this. At least not without resorting to a switch over a type, which is worse. Anyway, my suggestion creates a generic hook in the classes of your hierarchy which any code code can hook into. The `display_callback` could be deep in the GUI code, the game doesn't have to know it. It just knows the generic `app_callback` type.
sbi
This pattern is commonly known as 'visitor', and I believe that it is what you actually want: the lower layer only understands about a generic 'visitor' (callback in the answer), and you can create the concrete graphic display creator visitor within the GUI layer, using the lower layer only for dispatch.
David Rodríguez - dribeas
@sbi, I think you forgot to make "display_callback" inherit from "app_callback". Otherwise, very good answer.
Michael Aaron Safyan
@David: I wasn't sure whether to call this "Visitor". I have only ever seen it called "Visitor" when it the callback is applied to a sequence of objects.
sbi
@Michael: Ouch, that hurt. Thank you.
sbi
I am trying to implement this, but I am having some trouble what to include where: I now have `Base.h`, `Child.h`, `BaseCallback.h`, `BaseCallBackDisplayer.h` and `Displayer.h``Base.h` needs `BaseCallback.h`, `BaseCallBack.h` needs `Base.h` (and `Child.h`). Because both use virtual functions I cannot use abstract class definitions. How do I solve this?
Jasper
@Jasper: That's very confusing in a comment. Unless someone comes a long who is not as tired as I am and able to make sense of this, I suggest you open a new question, refer to this one, and paste the essence of these files into it. I'm sure it's easier to understand that way. (If you want, put a link to that question into a comment here, so SO will make me aware of that question.)
sbi
I'm sorry, never mind that comment, I thought it was a problem inherent to the code you provided, but it seems that in building something like it, I made a completely unrelated (and stupid mistake). I have been able to fix it now. Thanks for your help!
Jasper