For my project, I needed a way to instantiate different object types during runtime using "string names", for this I designed a generic factory that is created for each object hierarchy type (currently there are two different hierarchies that need the factory).
We want the factory to be simple to use and simple to register new types, for this, the factory relies on two classes types.
The first one, is the object creator, that each new class that needs to register on the factory instantiates, this is constructed as shown below:
template <typename T, typename Y>
class ObjectCreatorBase_c: public ObjectCreatorAutoUnlinkHook_t
{
public:
typedef T ObjectType_t;
typedef Y ObjectCreatorProc_t;
public:
ObjectCreatorBase_c(const String_c &name, ObjectCreatorProc_t proc):
strName(name),
pfnCreateProc(proc)
{
if(proc == NULL)
{
std::stringstream stream;
stream << "creator proc cant be null, entity " << name;
PH_RAISE(INVALID_PARAMETER_EXCEPTION, "[ObjectCreatorBase_c::ObjectCreatorBase_c]", stream.str());
}
}
T Create(const String_c &name) const
{
return pfnCreateProc(name);
}
inline const String_c &GetName() const
{
return strName;
}
inline bool operator<(const ObjectCreatorBase_c &rhs) const
{
return strName.compare(rhs.strName) < 0;
}
private:
String_c strName;
protected:
ObjectCreatorProc_t pfnCreateProc;
};
This base class is created because there are two types (right now) of creation function, one with a single parameter for the constructor and another with two. Each type is specialized as shown below:
template <typename T>
class ObjectCreator_c: public ObjectCreatorBase_c<T, T(*)(const String_c &)>
{
public:
typedef ObjectCreatorBase_c<T, ObjectCreatorProc_t> BaseType_t;
public:
ObjectCreator_c(const String_c &name, ObjectCreatorProc_t proc):
BaseType_t(name, proc)
{
GenericFactory_c<ObjectCreator_c<T> >::GetInstance().Register(*this);
}
};
template <typename T, typename Y>
class ObjectCreator1_c: public ObjectCreatorBase_c<T, T(*)(const String_c &, Y )>
{
public:
ObjectCreator1_c(const String_c &name, T(*proc)(const String_c &, Y ) ):
ObjectCreatorBase_c(name, proc)
{
GenericFactory1_c<ObjectCreator1_c, Y >::GetInstance().Register(*this);
}
T Create(const String_c &name, Y param) const
{
return pfnCreateProc(name, param);
}
};
The first creator just has a default string parameter that is common for all classes and the second one has an extra template parameter that can be customized.
Finally the factory is defined as show below:
template <typename T>
class GenericFactory_c: boost::noncopyable
{
public:
typedef typename T::ObjectType_t ObjectType_t;
static GenericFactory_c &GetInstance()
{
static GenericFactory_c<T> clInstance_gl;
return clInstance_gl;
}
ObjectType_t Create(const String_c &className, const String_c &name) const
{
return this->GetObjectCreator(className).Create(name);
}
protected:
GenericFactory_c() { }
friend T;
void Register(T &creator)
{
setObjectCreators.insert(creator);
}
const T &GetObjectCreator(const String_c &className) const
{
typename ObjectCreatorSet_t::const_iterator it = setObjectCreators.find(className, ObjectCreatorComp_s<T>());
if(it == setObjectCreators.end())
PH_RAISE(OBJECT_NOT_FOUND_EXCEPTION, "[EntityFactory_c::Create]", className);
return *it;
}
protected:
typedef boost::intrusive::set<T, boost::intrusive::constant_time_size<false> > ObjectCreatorSet_t;
ObjectCreatorSet_t setObjectCreators;
};
Also, there is a specialization of the factory for the object with an extra parameter:
template <typename T, typename Y>
class GenericFactory1_c: public GenericFactory_c<T>
{
public:
static GenericFactory1_c &GetInstance()
{
static GenericFactory1_c clInstance_gl;
return clInstance_gl;
}
ObjectType_t Create(const String_c &className, const String_c &name, Y param) const
{
return this->GetObjectCreator(className).Create(name, param);
}
};
By the last, I have created this "comparable" object for searching the set of creators:
template<typename T>
struct ObjectCreatorComp_s
{
bool operator()(const String_c &name, const T &res) const
{
return name.compare(res.GetName()) < 0;
}
bool operator()(const T &res, const String_c &name) const
{
return res.GetName().compare(name) < 0;
}
};
To help understand the code above, below I show how it is being used. For example, for defining a new type to be used with the factory, we use:
typedef GenericFactory<ObjectCreator_c<MyObject*> > MyObjectFactory_t;
Define a object creator for a concrete type:
static ObjectCreator<MyObject*> MyConcreteObject_CreatorObject("MyConcreteObject", MyConcreateObject::Create);
Where we assume that "MyConcreteObject" has a static method called "Create" that returns a new instance of the type.
Note also that the creator instance will auto-register itself on the appropriate factory.
For creating an object instance, we could use:
MyObject *obj = MyObjectFactory_t::GetInstance().Create("MyConcreteObject", "objectName");
Any thoughts or suggestion about the design or simple ways to do it are welcome!
Thank you
Foo
whose only constructor takes two mandatory arguments,Foo(char, double)
, how would ICreate
an instance of this class? \$\endgroup\$Create
method take an arbitrary number of arguments, or just one or none? Hmm.. to be honest, I can't really see through this vast amount of indirection and tell whether it makes sense. If you're set on this design, I can't say anything meaningful. If you're willing to consider alternatives, though, perhaps we could start higher up and think it over again. \$\endgroup\$