I have a design question that has been bugging me for a while but I cannot find a good (in a OOP sense) solution for this. The language is C++ and I keep coming back to RTTI - which is often referred to as an indicator for bad design.
Suppose we have a set of different kinds of modules implemented as different classes. Each kind of module is characterized by a defined interface, however the implementation may vary. Thus my first idea was to create an interface (pure abstract) class for each kind of module (e.g. IModuleFoo, IModuleBar etc.) and the implementations in seperate classes. So far so good.
class IModuleFoo {
public:
virtual void doFoo() = 0;
};
class IModuleBar {
public:
virtual void doBar() = 0;
};
On the other hand we have a set of (application) classes and each of them uses a couple of those modules but only through the interfaces - even the modules themselves might use other modules. However, all of the application classes will share the same pool of modules. My idea was to create a manager class (ModuleManager) for all modules which application classes can query for the module types they need. The available modules (and the concrete implementation) are set up during initialization of the manager and may vary over time (but that is not really part of my question).
Since the number of different module kinds is most probably >10 and may increase over time it does not appear suitable to me to store references (or pointers) to them separately. In addition there might be a couple of functions the manager needs to invoke on all managed modules. Thus I created another interface (IManagedModule) with the benefit that I can now use a container (list, set, whatsoever) of IManagedModules to store them in the manager.
class IManagedModule {
public:
virtual void connect() = 0;
{ ... }
};
The consequence is that a module that shall be managed needs to inherit both from the IManagedModule and from the appropriate interface for its type.
But things turn ugly when I think about the ModuleManager. It can be assumed that there is at most one instance of each module type present at each time. Thus if it was possible to do something like this (where manager is the instance of the ModuleManager), everything would be fine:
IModuleFoo* pFoo = manager.get(IModuleFoo);
But I'm pretty sure that it's not. I also thought about a template based solution like:
IModuleFoo* pFoo = manager.get<IModuleFoo>();
That could work but I have no idea how to find the right module within the manager if all I have is a set of IManagedModules - that is without the use of RTTI, of course.
One approach would be to provide IManagedModule with a virtual getId() method, rely on the implementations to use non-ambigous ids for each kind of module and do the pointer casting on your own. But that's just reinventing the wheel (namely RTTI) and requires a lot of discipline within the implementing classes (providing the right ids etc...) which is not desirable.
Long story short - the question is if there is really no way around some kind of RTTI here and in this case RTTI might even be a valid solution or if there might be a better (cleaner, safer, ...) design which exhibits the same flexibility (e.g. loose coupling between application classes and module classes...)? Did I miss anything?
I think bdonlan's suggestion is good, but requiring each module type to declare a distinct
INTERFACE_ID
is a maintenance headache. The distinctness can be accomplished automatically by having each module type declare a static object and using its address as the ID:In this case we are using
void *
as the interface ID type, instead ofint
or an enumerated type, so the types in some other declarations will need to change.Also, due to quirks in C++, the
INTERFACE_ID
values, despite being labelledconst
, are not "constant enough" to be used forcase
labels inswitch
statements (or array size declarations, or a handful of other places), so you would need to change theswitch
statement to anif
. As described in section 5.19 of the standard, acase
label requires an integral constant-expression, which roughly speaking is something the compiler can determine just from looking at the current translation unit; whileINTERFACE_ID
is a mere constant-expression, whose value cannot be determined until link time.It sounds like you're looking for something similar to COM's QueryInterface. Now, you don't need to implement COM entirely, but the basic principle stands: You have a base class, with a virtual function, to which you pass an identifier specifying which interface you want. The virtual function then looks to see if it can implement that interface, and if so, passes back a pointer to that interface.
For example:
It's a bit unwieldy, but it's not too bad, and without RTTI you don't have much of a choice besides an approach like this.