65.9K
CodeProject 正在变化。 阅读更多。
Home

动态类工厂

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (17投票s)

2004年7月25日

CPOL

3分钟阅读

viewsIcon

94717

downloadIcon

1400

一篇关于具有动态订阅/自动注册的类工厂的文章

引言

这是一篇关于类工厂可能实现的的文章,它使用动态订阅。它允许根据自定义条件创建对象,例如,在解析 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/值对)并定义相应的谓词函子。
  • 在类定义中,添加 staticcreator”函数和虚拟变量
    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 组合有一个类工厂实例。这对于大多数实例来说是可以的,但可能存在并非如此的情况——那么一个新的模板参数将会有所帮助。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.