Instanciate Objects of Unknown Type from Their Parent Interface



This is based on my previous two posts on Static Interfaces in C++ and Keep Track of and Enumerate All Sub-classes of a Particular Interface. The idea is that I want my code to be extensible in the feature without requiring any re-writing of the current code base. The code base operates on generic objects via their interfaces, so as long as newly-coded classes properly extend those interfaces, the program should know how to handle them. The problem is, how can we write the program in such a manner that a user interface can enumerate available options for implementations of a particular interface, and how can we instantiate those objects?

In Keep Track of and Enumerate All Sub-classes of a Particular Interface I showed how to maintain a registry of classes deriving from a given interface, which handles the first problem, but there is a limitation in that all of these classes must provide a factory method that takes no parameters (void input). I decided that, for my project, this was not acceptable and I needed a way to define the creation parameters as part of the factory methods, whereas the creation parameters may be different for particular interfaces.

In Keep Track of and Enumerate All Sub-classes of a Particular Interface I showed how we can enforce the requirement of a static method in derived classes with a particular signature using a template interface.

In this post I will combine the two so that we can create a registry of classes that inherit from a particular interface, and provide a static factory method for creating objects of that interface, using a particular creation method signature unique to that interface. The registry will pair class names with function pointers that match the specific signature of the interface the class is being registered for.

Disclaimer: I do not claim this is the "best" way to handle this issue. This is just what I came up with. It happens to be pretty involved and overly indirect, which means it's probably bad design. It is, however, an extremely interesting exercise in generic programming.

Prequil: the code will require these later so there they are:

/**
 *  file          RegistryTest.cpp
 *  date:      Aug 14, 2009
 *  brief:
 *
 *  detail:
 */


#include 
#include 
#include 
#include

using namespace std;

Ok, so lets begin. First let's define a couple of interfaces that we're interested in.

class InterfaceA{};
class InterfaceB{};
class InterfaceC{};

Now we create a template class whose sole purpose is to create a per-interface typedef of the function signature that is necessary for instantiating and object of that class. Is it really possible that all sub-objects can be instantiated with the same parameters? If that's the case, shouldn't they all be combined into a single class that just contains that information as private members? Probably, but in my case these parameters are more like a "bare minimum" for instantiation, and then many more parameters are set by the user. It makes sense to me, I promise. If it doesn't to you, you don't have to use this.

template< typename InterfaceType >
class Factory
{
    public:
        typedef InterfaceType*(*Creator)(void);
};

Creator is now a typedef that aliases a function pointer that takes no parameters. Wait, isn't that what we had before? Yes, but now we make a couple of template specializations to define the different signatures for our specific interfaces. These specializations would normally be in the file that contained the interface declaration.

/// specializations can define other creators, this one requires an int
template<> class Factory
{
    public:
        typedef InterfaceB*(*Creator)(int);
};

/// specializations can define other creators, this one requires an int, a
/// bool, and a char
template<> class Factory
{
    public:
        typedef InterfaceC*(*Creator)(int,bool,char);
};

Cool. Now we create a static interface that enforces it's derivative classes to contain a static method called createNew which can be used to instantiate a new object of that interface. We can use the typedef we just created to make the function signature generic for this template (or specific to individual instantiations of it).

template
class IStaticFactory
{
    public:
        IStaticFactory()
        {
            typename Factory::Creator check = ClassType::createNew;
            check = check;
        }
};

Still following? Good. Now we define the registry class template, which maps the class name of a derived class to a function pointer with an interface- specific signature that serves as a static factory for objects of the derived class, returning a pointer to that object of the type of the interface. See my previous post for details on this class.

template 
class Registry
{
    private:
        std::map< std::string, typename Factory::Creator > m_creatorMap;


        Registry(){}

    public:

        static Registry& getInstance();


        bool registerClass( const std::string& name,
                             typename Factory::Creator creator );


        std::set  getClassNames();


        typename
        Factory::Creator
        Registry::getCreator(
                std::string className );
};



// A convient macro to compact the registration of a class
#define RegisterWithInterface( CLASS, INTERFACE )                   
namespace                                                           
{                                                                   
    bool dummy_ ## CLASS =                                          
    Registry::getInstance().registerClass(     
            #CLASS, CLASS::createNew );                               
}









template 
Registry& Registry::getInstance()
{
    static Registry    registry;
    return registry;
}












template 
bool Registry::registerClass( const std::string& name,
                        typename Factory::Creator creator )
{
    m_creatorMap[name] = creator;
    return true;
}










template 
std::set  Registry::getClassNames()
{
    std::set   keys;

    typename
    std::map< std::string, InterfaceType* (*)(void) >::iterator pair;

    for( pair = m_creatorMap.begin(); pair != m_creatorMap.end(); pair++)
        keys.insert( pair->first );

    return keys;
}










template 
typename
Factory::Creator
Registry::getCreator(
        std::string className )
{
    return m_creatorMap[className];
}

The difference between this and the Registry in my previous post, is that this time the registry uses the generic Factory<InterfaceType>::Creator typedef to define the function pointer. This way, that pointer is forced to have the specific signature. Sweet!

Now lets write some derived classes of those interfaces.

class   DerivedA : public InterfaceA,
                    public IStaticFactory
{
    public:
        static InterfaceA*  createNew(){ return (InterfaceA*)1; }
};


RegisterWithInterface(DerivedA, InterfaceA);



class   DerivedB : public InterfaceB,
                    public IStaticFactory
{
    public:
        static InterfaceB*  createNew(int a){ return (InterfaceB*)2; }
};


RegisterWithInterface(DerivedB, InterfaceB);



class   DerivedC : public InterfaceC,
                    public IStaticFactory
{
    public:
        static InterfaceC*  createNew(int a, bool b, char c){ return (InterfaceC*)3; }
};

RegisterWithInterface(DerivedC, InterfaceC);

These classes are basically dummies, but inheriting from IStaticFactory… the compiler will enforce that they contain the static method createNew with the proper signature. Notice that InterfaceA uses the default template so the static factory in DerivedA takes no parameters, while InterfaceB and InterfaceC have specializations so the static factories in DerivedB and DerivedC have their respective parameters. Since this is just an example, the methods don't actually create new objects they just return pointers, but in reality this is where we would use new DerivedA(…) and so on.

Well that's it. Pretty cool huh? The compiler will enforce all this stuff for us so we can actually say to ourselves when we write new implementations months from now "If it compiles, it will be compatible."

Lastly, here's a little test case to run

int main()
{
    DerivedA    a;
    DerivedB    b;
    DerivedC    c;

    InterfaceA* pA;
    InterfaceB* pB;
    InterfaceC* pC;

    Factory::Creator makesObjectOfA =
            Registry::getInstance().getCreator("DerivedA");
    pA = (*makesObjectOfA)();

    Factory::Creator makesObjectOfB =
            Registry::getInstance().getCreator("DerivedB");
    pB = (*makesObjectOfB)(1);

    Factory::Creator makesObjectOfC =
            Registry::getInstance().getCreator("DerivedC");
    pC = (*makesObjectOfC)(1,false,'a');

    cout << "pA: " << pA << "n";
    cout << "pB: " << pB << "n";
    cout << "pC: " << pC << "n";

    return 0;
}

[caption id="attachment_79" align="aligncenter" width="279" caption="Output of the Test Program"]![Output of the Test Program](http://128.31.5.103/wordpress /wp-content/uploads/2009/08/registry2.jpg)[/caption]

Comments


Comments powered by Disqus