C++ 对象工厂






4.91/5 (8投票s)
一个 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
参考文献
- 工厂(软件概念)– 维基百科: http://http://en.wikipedia.org/wiki/Factory_(software_concept)
- 工厂方法模式 – 维基百科: http://http://en.wikipedia.org/wiki/Factory_method_pattern
- 抽象工厂模式 – 维基百科: http://http://en.wikipedia.org/wiki/Abstract_factory_pattern
- C++ 11 – 维基百科: http://en.wikipedia.org/wiki/C%2B%2B11#Lambda_functions_and_expressions)