Introduction
In my project, I am using the Object Factory Design Pattern [1]. I wanted to give a user an option to bring his own class to the Object Factory. It is simple: you define your class, register it with the factory, and that is it. I wanted the user to make minimum changes to code, and definitely not play with some code deep inside an application from where the registration function is called. There are results.
Classes
The Object Factory creates objects at run time in response to actions of a user (or flow of information.) Type of the object to create is unknown at compile time. The object factory needs object ID only to create an object, because it has object classes registered at compile time.
The object creation uses polymorphism, so the object classes should be derived from some abstract
class, like:
class CBase
{
public:
CBase(){}
virtual ~CBase(){}
....................................
};
Derived classes must be derived from the base class. Because the object registration with the factory should be done before object creation, the derived classes must have two static
functions: the creator function and the function to register the derived class with the object factory, and the unique static
type identifiers:
class CDerived:public CBase
{
public:
static const int m_id;
public:
CDerived(void) {}
virtual ~CDerived(void){}
static CBase* CreateDerved() {return new CDerived;}
static void RegisterDerivedClass(void);
................................................
};
There CreateDerived
is a Creator
function.
The type identifiers might be any objects: integers, strings, etc. For simplicity, I use integers.
Object Factory Class
The object factory keeps pairs Type Identifier - Creator Functions in some container that is a member of the factory class. A process of initialization of the container is called the class (type) registration. It seems that the most convenient container is a std::map
.
To provide encapsulation, the factory has to have member functions to register and unregister classes.
More often than not, the factory class is implemented as a Singleton:
class CFactory {
public:
typedef CBase* (*DerivedClassCreatorFn)(void);
private: CFactory() {}
CFactory(const CFactory&);
CFactory operator =(const CFactory&);
public:
~CFactory() {}
public:
static CFactory& Instance(void)
{
static CFactory factory;
return factory;
}
void RegisterClassCreator(int id, DerivedClassCreatorFn
creatorFn)
{
m_mapClassCreators.insert(std::map<int, />::value_type(id, creatorFn));
}
void UnregisterClassCreator(int id)
{
m_mapClassCreators.erase(id);
}
private:
std::map<int, /> m_mapClassCreators;
};
I have omitted error handling in RegisterClassCreator()
and UnregisterClassCreator()
.
The RegisterDerivedClass
function in the Derived
class simply calls RegisterClassCreator
function with appropriate type id and pointer to class Creator
Function.
So somewhere in an application registration is implemented in a loop like: for all classes DerivedClass::RegisterDerivedClass()
.
You have to provide fully classified names to call the static Creator
Functions. How to automatically get the type names for this loop? It is where Type Lists and template metaprogramming can help.
Type List
Loki free library has definition and code for type lists. I do not want to make you download and install Loki because we need only few definitions and defines.
This is straight from [2].
Definition of TypeList
template <class>
struct TypeList
{
typedef T Head;
typedef U Tail;
};
Null Type:
class NullType
{};
To make code less verbose, [2] introduced defines:
#define TYPELIST_1(T1) TypeList<t1>
#define TYPELIST_2(T1, T2) TypeList<t1>
#define TYPELIST_3(T1, T2, T3) TypeList<t1>
#define TYPELIST_4(T1, T2, T3, T4) TypeList<t1>
...........................................................
We will use a TypeList
in MetaLoop
to get class types.
How It Works
Suppose we have four classes to registers: CDerived0
, CDerived1
, CDerived2
, and CDerived2
with their type identifiers.
Let's define MetaLoop
:
template <class>
struct RegDerivedClasses;
Partial template specialization to stop loop unwinding:
template <class>
struct RegDerivedClasses<typelist<head>, 0>
{
typedef Head Result;
static inline void Fn(void)
{
Result::RegisterDerivedClass();
}
};
Partial Specialization to Unwind the Loop
template <class>
struct RegDerivedClasses<typelist<head>, idx>
{
typedef typename RegDerivedClasses<tail>::Result
Result;
static inline void Fn(void)
{
Result::RegisterDerivedClass();
ReDerivedClasses<typelist<head>, idx - 1>::Fn();
}
};
It seems like the keyword 'inline
' here is crucial: it instructs a compiler to inject code at the place of invocation.
Finally, let's define alias:
typedef RegDerivedClasses<typelist_4(derived0> RegClassStruct;
Now all you have to do to register the classes is to write in application:
RecClassStruct::Fn();
It is recommended to keep all code related to the Type List and struct RegDerivedClasses
in the same file that keeps code for the base and derived classes and the factory class.
Bring Your Own Class
Suppose you want to add your own class, CDerived4
, to the factory. Now you have five classes to register, four old classes and one new class.
Assuming you have access to the source file where CDerived
are defined, you proceed in steps:
Write new class, CDerived4
with mandatory members CreateDerived()
and RegisterDerivedClass()
and its unique identifier. If there is no #define
for next TYPELIST
, write a new:
#define TYPELIST_5(T1, T2, T3, T4, T5)
TypeList<t1>
Change last typedef;
typedef RegDerivedClasses<typelist_5(derived0> RegClassStruct;
Compile and run.
Of course, there might be another modification of executable code to utilize the newly added class, but it is a different matter.
Demo Application
The source code is pretty much explained above.
The demo application supplied with this article is compiled with Microsoft VC++ 2010 Express. (You can download VC++ 2010 Express from the Microsoft web site for free.)
The application registers four classes, CDerived0
to CDerived_3
with the factory and uses the factory to create class instances and writes class names onto console.
I also included folder Doc with HTML documentation files generated by Doxygen.
Open the file 'index.html' in your browser or any HTML reader and navigate to any place you want.
Literature
- Eric Gamma and others., "Design Patterns: Elements of Reusable Object-Oriented Software", Addison-Wesley
- Andrei Alexandrescu, "Modern C++ Design: Generic Programming and Design Patterns Applied", Addison-Wesley
History
- 8th June, 2010: Initial version