在软件开发中,工厂设计模式是一种常用的创建型设计模式,它提供了一种创建对象的最佳方式。这种模式允许客户端通过接口请求对象,而无需指定将要创建的对象的确切类。本文将介绍如何实现一个简单的对象工厂,并展示如何通过模板元编程简化类的注册过程。
对象工厂负责在运行时根据用户的操作或信息流动态创建对象。在编译时,要创建的对象类型是未知的,工厂只需要一个对象ID来创建对象,因为它在编译时已经注册了对象类。对象的创建利用了多态性,因此对象类应该从某个抽象类派生。
class CBase {
public:
CBase() {}
virtual ~CBase() {}
};
派生类必须从基类派生,并且在对象创建之前需要注册到工厂中。因此,派生类必须有两个静态函数:创建函数和注册函数,以及唯一的静态类型标识符。
class CDerived : public CBase {
public:
static const int m_id;
CDerived() {}
virtual ~CDerived() {}
static CBase* CreateDerived() {
return new CDerived;
}
static void RegisterDerivedClass();
};
其中CreateDerived是创建函数,类型标识符可以是任何对象,如整数、字符串等。为了简化,这里使用整数。
对象工厂类维护一个类型标识符-创建函数对的容器,该容器是工厂类的一个成员。容器的初始化过程称为类(类型)注册。通常,工厂类被实现为单例模式。
class CFactory {
public:
typedef CBase* (*DerivedClassCreatorFn)();
private:
CFactory() {}
CFactory(const CFactory&);
CFactory& operator=(const CFactory&);
public:
static CFactory& Instance() {
static CFactory factory;
return factory;
}
void RegisterClassCreator(int id, DerivedClassCreatorFn creatorFn) {
m_mapClassCreators.insert(std::map::value_type(id, creatorFn));
}
void UnregisterClassCreator(int id) {
m_mapClassCreators.erase(id);
}
private:
std::map m_mapClassCreators;
};
在RegisterClassCreator和UnregisterClassCreator函数中省略了错误处理。
为了简化类的注册过程,可以使用类型列表和模板元编程。Loki自由库提供了类型列表的定义和代码,但本文将直接提供所需的定义和宏定义。
template
struct TypeList {
typedef T Head;
typedef U Tail;
};
为了使代码更简洁,引入了宏定义。
#define TYPELIST_1(T1) TypeList
#define TYPELIST_2(T1, T2) TypeList
#define TYPELIST_3(T1, T2, T3) TypeList
// ...
使用TypeList在MetaLoop中获取类类型。
假设有四个类需要注册:CDerived0、CDerived1、CDerived2和CDerived3,以及它们各自的类型标识符。定义MetaLoop如下:
template
struct RegDerivedClasses;
template
struct RegDerivedClasses, idx> {
typedef Head Result;
static inline void Fn() {
Result::RegisterDerivedClass();
}
};
template
struct RegDerivedClasses, idx> {
typedef typename RegDerivedClasses, idx-1>::Result Result;
static inline void Fn() {
Result::RegisterDerivedClass();
RegDerivedClasses, idx-1>::Fn();
}
};
最后,定义别名:
typedef RegDerivedClasses RegClassStruct;
现在,要注册类,只需在应用程序中写入:
RegClassStruct::Fn();
建议将所有与类型列表和RegDerivedClasses结构相关的代码与基类、派生类和工厂类代码保持在同一文件中。
假设想将自己的类CDerived4添加到工厂中。现在有五个类需要注册,四个旧类和一个新类。假设可以访问定义CDerived的源文件,按照以下步骤进行:
编写新类CDerived4,包括必需的成员CreateDerived和RegisterDerivedClass以及其唯一标识符。如果没有下一个TYPELIST的#define,编写一个新的:
#define TYPELIST_5(T1, T2, T3, T4, T5) TypeList
typedef RegDerivedClasses RegClassStruct;
当然,可能还需要修改可执行代码以使用新添加的类,但这是另一回事。
源代码在上面已经解释得很清楚了。本文附带的演示应用程序是用Microsoft VC++2010 Express编译的。(可以从Microsoft网站免费下载VC++ 2010 Express。)该应用程序注册了四个类CDerived0到CDerived3到工厂,并使用工厂创建类实例,并将类名写入控制台。