动态类工厂
一篇关于具有动态订阅/自动注册的类工厂的文章
引言
这是一篇关于类工厂可能实现的的文章,它使用动态订阅。它允许根据自定义条件创建对象,例如,在解析 XML 或其他类型的数据时,或用于创建从数据库加载的对象时,非常有用。
背景
你是否遇到过不得不编写一个巨大的 switch
命令,除了创建新对象之外什么都不做的情况?并将所有对象的头文件包含在一个 CPP 文件中?然后,当你更改其中一个头文件时,编译它需要很长时间?
类似于这样
CBase* pBase(NULL);
switch (type_variable)
{
case obj1: pBase = new CBaseDerivate1();break;
case obj2: pBase = new CBaseDerivate2();break;
...
case objN: pBase = new CBaseDerivateN();break;
}
或者更糟
CBase* pBase(NULL);
if (!strcmp(string_for_type, "Type1")) pBase = new CBaseDerivate1();
else if (!strcmp(string_for_type, "Type2")) pBase = new CBaseDerivate2();
...
else if (!strcmp(string_for_type, "TypeN")) pBase = new CBaseDerivateN();
附言:当使用复杂的条件时,情况可能会更糟。
解决问题
当我在实现 XML 解析器并从数据库加载存储的对象时,我得到了很多类似的结构。好吧,我很快就厌倦了。在变得更有经验之后,我开始思考这个问题。最近,当我又需要使用类似的代码时,我发现了以下方法...
我真正想要的是一个“类工厂”,可以为我创建类。
CBase* pBase = CClassFactory::CreateInstance(type_variable);
我可以将对象构造隐藏到一个单独的类工厂中。在 ATL 中,类工厂使用一个静态的可用类数组,其中包含指向 CreateInstance
函数的指针。新条目由向导添加。但标头和编译的问题(仍然会有很多包含会进入 CPP)仍然存在。
然后,我想到一个主意——静态数组/映射怎么样?但动态填充?并且所有类都会“注册”它们自己的创建者函数。这可能吗?怎么做?
动态填充静态数组/映射的想法一开始听起来很奇怪,但是声明 std::map
/vector
为静态的怎么样?并通过一些巧妙的机制在以后填充它...是的,我们可以做到。
现在,关于自我注册部分。我想了一会儿才想出解决方案,但它很简单(一旦找到)。我们可以偷偷地在静态虚拟变量的初始化中加入一些代码...比如
int CMyClass::iDummy =
ClassFactory::RegisterCreatorFunction(key, CMyClass::Creator)
其中函数 RegisterCreatorFunction
将返回任何整数,并将创建者函数指针(也必须是静态的)添加到数组/映射中。
好吧,你可能会说...这看起来很容易做到。
所以,现在我们有
static std::map<key, pointer_to_create_function> mapOfCreators;
class CBaseDerivate1: public CBase
{
static CBase* create_function();
static key DummyVariable;
}
在头文件中,并且
key CBaseDerivate1::DummyVariable = RegisterCreatorFunction(key,
CBaseDerivate1::create_function)
在 CPP 文件中。
如果你尝试一下,有 50% 的机会它会起作用。是的,这里有一个陷阱:可能会发生静态虚拟变量在静态数组/映射初始化之前被初始化。并且整个代码将抛出一个未处理的异常。但有一个补救措施——我们不会直接访问静态数组,而是通过静态的 get
函数,因此数组/映射将在第一次访问时创建...
所以,而不是
static std::map<key, pointer_to_create_function> mapOfCreators;
我们需要一个“get
”函数
static std::map<key, pointer_to_create_function> * get_mapOfCreators()
{
static std::map<key, pointer_to_create_function> _mapOfCreators;
return &_mapOfCreators;
}
解决方案
当我们把所有东西封装到一个类中时,我们可以得到类似
template <typename _Key, typename _Base,
typename _Predicator = std::less<_Key> >
class CClassFactory
{
public:
CClassFactory() {};
~CClassFactory() {};
typedef _Base* (*CreatorFunction) (void);
typedef std::map<_Key, CreatorFunction, _Predicator> _mapFactory;
// called at the beginning of execution to register creation functions
// used later to create class instances
static _Key RegisterCreatorFunction(_Key idKey,
CreatorFunction classCreator)
{
get_mapFactory()->insert(std::pair<_Key,
CreatorFunction>(idKey, classCreator));
return idKey;
}
// tries to create instance based on the key
// using creator function (if provided)
static _Base* CreateInstance(_Key idKey)
{
_mapFactory::iterator it = get_mapFactory()->find(idKey);
if (it != get_mapFactory()->end())
{
if (it->second)
{
return it->second();
}
}
return NULL;
}
protected:
// map where the construction info is stored
// to prevent inserting into map before initialisation takes place
// place it into static function as static member,
// so it will be initialised only once - at first call
static _mapFactory * get_mapFactory()
{
static _mapFactory m_sMapFactory;
return &m_sMapFactory;
}
};
总结一下
现在,一切都准备好使用了。为此,有必要
- 决定使用什么作为键(例如,整数/字符串/GUID/值对)并定义相应的谓词函子。
- 在类定义中,添加
static
“creator
”函数和虚拟变量static CSampleBase* SampleCreatorFunction() {return new CSampleOne;}
static int iDummyNr;
- 要初始化虚拟变量,请调用代码来注册
creator
函数int CSampleOne::iDummyNr = CClassFactory<int, CSampleBase >::RegisterCreatorFunction(1, CSampleOne::SampleCreatorFunction);
- 要创建一个对象,只需调用
CSampleBase * pBase = CClassFactory<int, CSampleBase >::CreateInstance(1);
已知约束
如果你不开始使用多线程,这个类将完美运行。众所周知(?),MS <map>
本身在多线程环境中无法很好地工作,所以这将是首先要关注的地方... 我的解决方案是使用关键部分来保护所有类调用,但这并不是一个通用的解决方案,所以我不会在这里包含它。
此外,这里呈现的方式只允许每个 Key/Base/Predicator 组合有一个类工厂实例。这对于大多数实例来说是可以的,但可能存在并非如此的情况——那么一个新的模板参数将会有所帮助。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。