ChefZ -- 磨劍錄 (A Coder's Log)

慣看秋月春風 一壺濁酒喜相逢 古今多少事 皆賦談笑中
posts - 42, comments - 3, trackbacks - 0, articles - 6

C++ Class Factory

Posted on 2008-12-05 20:06 chefZ 阅读(560) 评论(0)  编辑 收藏 引用
#include <map>
#include <functional>
#include <exception>
#include <stdexcept>

#include <memory>
#include <iostream>

template<class TSrcType>
class TypeID : public std::unary_function<TSrcType, TSrcType>
{
public:
     typedef TSrcType objTypeId;
};

template<class TKeyType, class TBaseType>
class TObjFactory
{
public:
     typedef TBaseType * value_type;

     TObjFactory(void) {}
     ~TObjFactory(void)
     {
          typename typeMapKeyToBuilder::iterator it(m_mapKeyToBuilder.begin()),itend(m_mapKeyToBuilder.end());
          for(;it != itend; ++it)
               delete it->second;
     }

     template<typename TSubType>
          void     registerBuilder(const TKeyType & key,TypeID<TSubType> obj)
          {
               typedef typename TypeID<TSubType>::objTypeId     srcType;
               typename typeMapKeyToBuilder::iterator it = m_mapKeyToBuilder.find(key);
               if (it != m_mapKeyToBuilder.end())///need to allow for mutiple instance
                    throw std::runtime_error("duplicate");
               m_mapKeyToBuilder[key] = new TObjBuilder<srcType>();
          }

     value_type     buildObj(const TKeyType & key)
     {
          typename typeMapKeyToBuilder::iterator it = m_mapKeyToBuilder.find(key);
          if (it == m_mapKeyToBuilder.end())
               throw std::runtime_error("not found");
          return it->second->buildObject();
     }
protected:
     class TObjBuilderBase
     {
     public:
          virtual value_type     buildObject(void) = 0;
     };

     template<class TSubType>
     class TObjBuilder : public TObjBuilderBase
     {
     public:
          virtual value_type     buildObject(void)
               {     return new TSubType();     }
     };

     typedef TObjBuilderBase *                         typeBuilderPtr;
     typedef std::map<TKeyType,typeBuilderPtr>     typeMapKeyToBuilder;
     typeMapKeyToBuilder          m_mapKeyToBuilder;
};
//---test---------------------

class Base
{
public:
     virtual void test(void) = 0;
};

class classA : public Base
{
public:
     virtual void test(void) { std::cout << 'A' << std::endl; }
};

class classB : public Base
{
public:
     virtual void test(void) { std::cout << 'B' << std::endl; }
};

int main(void)
{
     TObjFactory<int,Base>     myFactory;
     myFactory.registerBuilder(0,TypeID<classA>());
     myFactory.registerBuilder(1,TypeID<classB>());

     std::auto_ptr<Base> auA(myFactory.buildObj(0));
     std::auto_ptr<Base> auB(myFactory.buildObj(1));

     auA->test();
     auB->test();
    
     return 0;
}

+++++++++++++++++++++++++++++++++++++++++++++++++++

Introduction

Sometimes you need to create instances of classes in parts of an application where you don't really need to know what the class details are. You just want to create an instance.

This is often solved by using factories. Constructions (in most cases these are classes) that have the single purpose of simply creating other instances. This article presents some different alternatives for writing such factories. Each with their own advantages and disadvantages.


Factory 1: the simple factory

Example 1 contains a simple factory class.

class SimpleClassFactory
{
public:
SimpleClass *createInstance() {return new SimpleClass;}
};
This class simply creates an instance of another class. This approach can be used if the number of different classes is limited. You then only need to write a limited number of class factories. The disadvantage of this approach is that if you have many different classes, that you need to write many factory classes as well. The following alternative tries to solve this.

Factory 2: the multi-factory

Example 2 contains a factory that is able to create different kinds of instances, depending on the given arguments. Suppose that we have the following class hierarchy:

class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
};

class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
private:
int m_value;
};

class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
private:
int m_value;
};
A multi-factory implementation might look like this:

class SimpleClassFactory
{
public:
SimpleBaseClass *createInstance1() {return new SimpleClass1;}
SimpleBaseClass *createInstance2() {return new SimpleClass2;}
};
This approach can be used if you have a central point that knows about all the different classes that need to be instantiated, and the caller of the factory knows which class he wants to instantiate. This multi-factory cannot be used if the caller of the factory does now know which classes could be generated by the factory and only passes some magic value that needs to be dispatched to the correct instantiation.

Factory 3: the dispatching multi-factory

This approach is very similar to the previous one, but uses a dispatching method.

class SimpleClassFactory
{
public:
SimpleBaseClass *createInstance(int type);
};

SimpleBaseClass *SimpleClassFactory::createInstance (int type)
{
if (type==1) return new SimpleClass1;
else if (type==2) return new SimpleClass2;
else return NULL;
}
If one needs to create an instance of a certain type (1 or 2), it can create the instance like this:

SimpleBaseClass    *simpleInstance     = NULL;
SimpleClassFactory simpleClassFactory;
simpleInstance = simpleClassFactory.createInstance(1);
I admit that this is only a small difference with approach 2, but it can be useful in some situations.

Factory 4: the function-pointer-factory

The multi-factory has the disadvantage that the factory class needs to know about all the different classes. It cannot be used in the following situation:

Suppose that we have 10 classes. 5 of them are part of a module that is used in multiple applications. 5 of them are application-specific. They all inherit from the same base class. In some part of the application we need to create an instance of one of these classes. The exact class that needs to be instantiated depends on given value.

  • We cannot write the factory in the shared part since it only knows about 5 of the classes.
  • We don't want to write the factory in the application specific part since that requires us to copy it over multiple applications AND it can cause problems if a 6th shared class is added to the shared module (some applications might forget to add it to the application-specific-factory).
We could solve this problem by using function pointers, like this:

class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
static SimpleBaseClass *createInstance() {return new SimpleClass1;}
private:
int m_value;
};
class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
static SimpleBaseClass *createInstance() {return new SimpleClass2;}
private:
int m_value;
};
Each class is given a static 'createInstance()' method that creates an instance of the class. Our method that needs to create an instance based on a given value, might look like this:

SimpleBaseClass *someMethod (
std::map<int,FactoryFunction> factoryFunctions, int type)
{
return factoryFunctions[type]();
}
The application can feed it with the supported factories like this:

std::map<int,FactoryFunction>  factoryFunctions;
factoryFunctions[1] = &SimpleClass1::createInstance;
factoryFunctions[2] = &SimpleClass2::createInstance;
The method is then called like this:

simpleInstance = someMethod (factoryFunctions,1);
The advantage is that it is simple to use and that it doesn't require a factory per class or one big dispatching multi-factory that needs to know about all the classes. Also the solution seems understandable for developers with a pure C background. However, passing function pointers in C++ is sometimes regarded as 'not done', and in the following alternatives I'll give some other approaches.

Factory 5: the clone-factory

The clone factory approach makes use of 'dummy' instances that are cloned. We need to give each class that needs to be instantiated by someone a clone method, like this:

class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
virtual SimpleBaseClass *clone() = 0;
};

class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
SimpleBaseClass *clone() {return new SimpleClass1(*this);}
private:
int m_value;
};

class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
SimpleBaseClass *clone() {return new SimpleClass2(*this);}
private:
int m_value;
};
The method that needs to create the instances then needs a map of these 'dummy' instances instead of function pointers:

SimpleBaseClass *someMethod (
std::map<int,SimpleBaseClass *> clonables, int type)
{
return clonables[type]->clone();
}
And using it works like this:

SimpleBaseClass                 *simpleInstance     = NULL;
SimpleClass1 clonable1;
SimpleClass2 clonable2;
std::map<int,SimpleBaseClass *> clonables;

clonables[1] = &clonable1;
clonables[2] = &clonable2;

simpleInstance = someMethod (clonables,1);
The advantage of this approach is that we got rid of the function pointers. However, the price we have to pay is that we need those dummy instances. If the classes that need to be instantiated are simple (and don't use excessive memory) that might be a solution. In other situations where the class instances need lots of memory, or represent more physical things (files, sockets, ...) it can be annoying to have these dummy instances.

Factory 6: the template-factory

With this approach we try to write our factory using templates. First we foresee the following two template classes:

template <class BT>
class FactoryPlant
{
public:
FactoryPlant() {}
virtual ~FactoryPlant() {}
virtual BT *createInstance() = 0;
};

template <class BT,class ST>
class Factory : public FactoryPlant
{
public:
Factory() {}
virtual ~Factory() {}
virtual BT *createInstance() {return new ST;}
};
The FactoryPlant class is the super class for the actual factories. In our previous examples, all the classes that could be instantiated, all derived frm the same base class. This is logical because otherwise it would be impossible to write a 'generic' factory for these classes. The type given to the FactoryPlant is the base class of the instances that will be created by the factories inheriting from this FactoryPlant. The Factory class is given two types: the base class and the actual class that is instantiated.

As you can see the FactoryPlant base class already contains the pure virtual createInstance method. The Factory class implements the method, since it knows which sub class type is actually instantiated. We can now give our classes their own factory like this:

class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
static Factory<SimpleBaseClass,SimpleClass1> myFactory;
private:
int m_value;
};
Factory<SimpleBaseClass,SimpleClass1> SimpleClass1::myFactory;

class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
static Factory<SimpleBaseClass,SimpleClass2> myFactory;
private:
int m_value;
};
Factory<SimpleBaseClass,SimpleClass2> SimpleClass2::myFactory;
Instead of a function pointer, the class has a static factory instance. Notice that, since it is static, that we need to define it outside the class as well. Otherwise we will have unresolved externals. To simplify the use of types, we also added a type definition to our base class:

class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
typedef FactoryPlant<SimpleBaseClass> SimpleBaseClassFactory;
};
Our method that needs to create instances of each of these classes, based on the magic number, now looks like this:

SimpleBaseClass *someMethod (
std::map<int,SimpleBaseClass::SimpleBaseClassFactory *> factories,
int type)
{
return factories[type]->createInstance();
}
It is passed a map of factories (all deriving from SimpleBaseClass::SimpleBaseClassFactory, which is actually FactoryPlant<SimpleBaseClass>), and it calls the createInstance of the factory (depending on the given magic number). The application can now call this like this:

SimpleBaseClass     *simpleInstance     = NULL;
std::map<int,SimpleBaseClass::SimpleBaseClassFactory *> factories;

factories[1] = &SimpleClass1::myFactory;
factories[2] = &SimpleClass2::myFactory;

simpleInstance = someMethod (factories,1);
In this example the build-up of the factories map is kept quite simple. In more complex situations part of the map could be created by a shared module. The application that simply needs to add his own factories (it wants to support) to the map.

Conclusion

There are many different methods of writing factories. I presented 6 of them here, each with their own advantages and disadvantages. In my situation (the reason I wrote this article), I actually needed the last one. I could use the function-pointer approach but using C-style-function pointers did not look like good C++ to me. I wasn't really fond of the clone-alternative either, so I started experimenting with the template approach. I hope this article might give you some ideas, and if you have some more ideas on writing factories, just send me a mail.

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理