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

C++ 对象工厂

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (8投票s)

2013年3月25日

CPOL

5分钟阅读

viewsIcon

60265

一个 C++ 对象工厂。

问题

在面向对象编程中,经常会出现这样的场景:大量类共享同一个基类,并且需要在运行时根据某些特定参数(例如字符串中保存的类名)创建具体的实现类。

标准 C++ 不提供其他语言(如 C#)用来实现这一功能的反射机制。

System.Reflection.Assembly.GetExecutingAssembly()
      .CreateInstance(string className)

或者 Java 中的反射机制。

Class.forName(className).getConstructor(String.class).newInstance(arg);

然而,C++ 不允许我们直接这样做;我们必须想出其他解决方案。帮助我们实现这一目标的基本模式或模式集是工厂模式。

简单的解决方案

基类

我们的基类定义为抽象类,如下所示:

#ifndef CPPFACTORY_MYBASECLASS_H
#define CPPFACTORY_MYBASECLASS_H

class MyBaseClass
{
public:
    virtual void doSomething() = 0;
};

#endif // CPPFACTORY_MYBASECLASS_H

工厂类

然后,工厂方法可以定义为静态方法,用于创建 MyBaseClass 的实例。我们可以将其定义为 MyBaseClass 本身的一个静态方法,但通常在面向对象开发中,一个类服务于单一目的是一个好的实践。因此,我们创建一个工厂类。

#ifndef CPPFACTORY_MYFACTORY_H
#define CPPFACTORY_MYFACTORY_H

#include "MyBaseClass.h"
#include <memory>
#include <string>

using namespace std;

class MyFactory
{
public:
    static shared_ptr<MyBaseClass> CreateInstance(string name);
};

#endif // CPPFACTORY_MYFACTORY_H

工厂方法预计会创建一个名为 name 的、派生自 MyBaseClass 的类的实例,并将其作为共享指针返回,因为它会将对象的所有权转让给调用者。

我们稍后会回到该方法的实现。

一些派生类

所以,让我们实现几个派生类。

#ifndef CPPFACTORY_DERIVEDCLASSONE_H
#define CPPFACTORY_DERIVEDCLASSONE_H

#include "MyBaseClass.h"
#include <iostream>
using namespace std;

class DerivedClassOne : public MyBaseClass
{
public:
    DerivedClassOne(){};
    virtual ~DerivedClassOne(){};

    virtual void doSomething() { cout << "I am class one" << endl; }
};

#endif // CPPFACTORY_DERIVEDCLASSONE_H

#ifndef CPPFACTORY_DERIVEDCLASSTWO_H
#define CPPFACTORY_DERIVEDCLASSTWO_H

#include "MyBaseClass.h"
#include <iostream>
using namespace std;

class DerivedClassTwo : public MyBaseClass
{
public:
    DerivedClassTwo(){};
    virtual ~DerivedClassTwo(){};

    virtual void doSomething() { cout << "I am class two" << endl; }
};

#endif // CPPFACTORY_DERIVEDCLASSTWO_H

工厂方法的第一次尝试

工厂方法实现的一个简单解决方案如下所示:

#include "MyFactorySimple.h"

#include "DerivedClassOne.h"
#include "DerivedClassTwo.h"

shared_ptr<MyBaseClass> MyFactory::CreateInstance(string name)
{
    MyBaseClass * instance = nullptr;

    if(name == "one")
        instance = new DerivedClassOne();

    if(name == "two")
        instance = new DerivedClassTwo();

    if(instance != nullptr)
        return std::shared_ptr<MyBaseClass>(instance);
    else
        return nullptr;
}

工厂决定创建哪个具体类,并通过类头文件了解所有类。

运行应用程序

现在需要一个简单的 main 函数来测试我们的实现。

#include "MyFactorySimple.h"

int main(int argc, char** argv)
{
    auto instanceOne = MyFactory::CreateInstance("one");
    auto instanceTwo = MyFactory::CreateInstance("two");

    instanceOne->doSomething();
    instanceTwo->doSomething();

    return 0;
}

本文附带的源代码中包含了一个 Visual Studio 项目 (SimpleFactory.vcxproj),可以生成并运行该项目,得到以下输出:

I am class one
I am class two

简单解决方案的问题

表面上看,这似乎是一个不错的解决方案,在某些情况下可能确实如此。但是,如果我们有很多类派生自 MyBaseClass 怎么办?我们仍然需要添加头文件和进行比较-构造代码。

现在的问题是,工厂对所有派生类都有显式依赖,这并不理想。我们需要想出更好的解决方案;一个能消除不断向 MyFactory::Create 添加内容的需要。这时,工厂方法注册表的想法就可以帮助我们。

修订后的工厂类

我们的主要目标之一是消除工厂对派生类的依赖。但是,我们仍然需要允许工厂触发实例的创建。一种方法是让主工厂类维护一个工厂函数的注册表,这些函数可以在其他地方定义。当工厂类需要创建派生类实例时,它可以在注册表中查找工厂函数。注册表定义如下:

map<string, function<MyBaseClass*(void)>> factoryFunctionRegistry;

它是一个映射,以字符串为键,值为返回指向基于 MyBaseClass 的类的实例的指针的函数。

然后,我们可以在 MyFactory 上有一个方法来将工厂函数添加到注册表中。

void MyFactory::RegisterFactoryFunction(string name,
function<MyBaseClass*(void)> classFactoryFunction)
{
    // register the class factory function
    factoryFunctionRegistry[name] = classFactoryFunction;
}

现在可以按如下方式修改 Create 方法:

shared_ptr<MyBaseClass> MyFactory::Create(string name)
{
    MyBaseClass * instance = nullptr;

    // find name in the registry and call factory method.
    auto it = factoryFunctionRegistry.find(name);
    if(it != factoryFunctionRegistry.end())
        instance = it->second();

    // wrap instance in a shared ptr and return
    if(instance != nullptr)
        return std::shared_ptr<MyBaseClass>(instance);
    else
        return nullptr;
}

那么,我们如何以最小化依赖关系的方式注册类呢?我们无法轻易地让派生类实例自己注册,因为在类未注册的情况下我们无法创建实例。我们需要类被注册而不是对象这一事实为我们提供了一个线索,我们可能需要一些静态变量或成员来做到这一点。

我强调,我将要做的这一点可能并非在所有情况下都是最佳的。我对静态变量和成员深表怀疑,因为静态初始化可能是一个雷区。但是,我会继续下去,因为这个解决方案可以满足本示例的用途,由读者决定他们使用的解决方案是否需要遵循不同的规则和设计。

首先,我们定义一个 MyFactory 方法来获取单例实例。

MyFactory * MyFactory::Instance()
{
    static MyFactory factory;
    return &factory;
}

我们不能从全局上下文调用以下代码:

MyFactory::Instance()->RegisterFactoryFunction(name, classFactoryFunction);

因此,我创建了一个 Registrar 类,它将在其构造函数中为我们执行调用。

class Registrar {
public:
    Registrar(string className, function<MyBaseClass*(void)> classFactoryFunction);
};
...
Registrar::Registrar(string name, function<MyBaseClass*(void)> classFactoryFunction)
{
    // register the class factory function 
    MyFactory::Instance()->RegisterFactoryFunction(name, classFactoryFunction);
}

有了这个之后,我们就可以在派生类的源文件中创建这个类的静态实例,如下所示(以 DerivedClassOne 为例):

static Registrar registrar("one",
[](void) -> MyBaseClass * { return new DervedClassOne();});

结果发现,这段代码可以复制到所有派生类中,因此可以使用一个快速的预处理器定义,如下所示:

#define REGISTER_CLASS(NAME, TYPE) \
    static Registrar registrar(NAME, \
        [](void) -> MyBaseClass * { return new TYPE();});

这利用了新的 C++ lambda 支持来声明匿名函数。然后,我们只需要在每个派生类的源文件中添加以下内容:

REGISTER_CLASS("one", DerivedClassOne);

更新 2013 年 1 月 25 日

我们可以做得更好……

虽然 #define 解决方案提供了简洁的实现,但我们也许可以通过将 Registrar 类转换为模板类来以更 C++ 的风格来实现这一点,如下所示:

template<class T>
class Registrar {
public:
    Registrar(string className)
    {
        // register the class factory function 
        MyFactory::Instance()->RegisterFactoryFunction(name,
                [](void) -> MyBaseClass * { return new T();});
    }
};

现在我们可以用以下方式替换宏的使用:

static Registrar<DerivedClassOne> registrar("one");

现在我们已经定义了一个基于函数注册表的工厂类,并且可以稍微修改 main 函数如下:

#include "MyFactory.h"

int main(int argc, char** argv)
{
    auto instanceOne = MyFactory::Instance()->Create("one");
    auto instanceTwo = MyFactory::Instance()->Create("two");

    instanceOne->doSomething();
    instanceTwo->doSomething();

    return 0;
}

现在我们可以构建并运行项目,得到以下输出:

I am class one
I am class two

参考文献

© . All rights reserved.