对象工厂设计模式:使用 TypeLists 和模板元编程注册创建者函数
如何使用 TypeList 和模板元编程注册对象工厂的类
引言
在我的项目中,我使用了对象工厂设计模式[1]。我想给用户一个选项,让他们能够将自己的类添加到对象工厂中。这很简单:您定义自己的类,将其注册到工厂,然后就完成了。我希望用户对代码的更改最少,并且绝对不要去修改应用程序中调用注册函数的深层代码。结果是令人满意的。
类
对象工厂响应用户(或信息流)的动作,在运行时创建对象。对象的类型在编译时是未知的。对象工厂只需要对象 ID 即可创建对象,因为它在编译时已经注册了对象类。
对象创建使用多态性,因此对象类应派生自某个 abstract
类,例如
class CBase
{
public:
CBase(){}
virtual ~CBase(){}
....................................
};
派生类必须继承自基类。因为对象必须在创建对象之前注册到工厂,所以派生类必须具有两个 static
函数:创建者函数和将派生类注册到对象工厂的函数,以及唯一的 static
类型标识符。
class CDerived:public CBase
{
public:
static const int m_id;
public:
CDerived(void) {}
virtual ~CDerived(void){}
static CBase* CreateDerved() {return new CDerived;}
static void RegisterDerivedClass(void);
................................................
};
这里 CreateDerived
是一个 Creator
函数。
类型标识符可以是任何对象:整数、字符串等。为简单起见,我使用整数。
对象工厂类
对象工厂在一个容器中保存类型标识符 - 创建者函数的配对,该容器是工厂类的一个成员。初始化容器的过程称为类(类型)注册。看起来最方便的容器是 std::map
。
为了实现封装,工厂必须有注册和注销类的成员函数。
工厂类通常实现为单例模式
class CFactory // Scott Meyer's Singleton
{
public:
typedef CBase* (*DerivedClassCreatorFn)(void);
private: // Singleton stuff
CFactory() {}
CFactory(const CFactory&);
CFactory operator =(const CFactory&);
public:
~CFactory() {}
public:
static CFactory& Instance(void)
{
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()
中省略了错误处理。
Derived
类中的 RegisterDerivedClass
函数只需调用 RegisterClassCreator
函数,并传入相应的类型 ID 和指向类 Creator
函数的指针。
因此,应用程序中的某个地方会以循环方式实现注册:针对所有类 DerivedClass::RegisterDerivedClass()
。
您必须提供完全限定的名称来调用 static Creator
函数。如何自动获取此循环的类型名称?这正是类型列表和模板元编程可以提供帮助的地方。
类型列表
Loki 免费库提供了类型列表的定义和代码。我不想让您下载和安装 Loki,因为我们只需要一些定义和宏。
这直接取自[2]。
类型列表定义
template <class>
struct TypeList
{
typedef T Head;
typedef U Tail;
};
Null Type:
class NullType
{};
为了使代码不那么冗长,[2] 引入了宏定义。
#define TYPELIST_1(T1) TypeList<t1>
#define TYPELIST_2(T1, T2) TypeList<t1>
#define TYPELIST_3(T1, T2, T3) TypeList<t1>
#define TYPELIST_4(T1, T2, T3, T4) TypeList<t1>
...........................................................
我们将在 MetaLoop
中使用 TypeList
来获取类类型。
工作原理
假设我们要注册四个类:CDerived0
、CDerived1
、CDerived2
和 CDerived2
,以及它们的类型标识符。
让我们定义 MetaLoop
。
template <class>
struct RegDerivedClasses;
Partial template specialization to stop loop unwinding:
template <class>
struct RegDerivedClasses<typelist<head>, 0>
{
typedef Head Result;
static inline void Fn(void)
{
Result::RegisterDerivedClass();
}
};
偏特化以展开循环
template <class>
struct RegDerivedClasses<typelist<head>, idx>
{
typedef typename RegDerivedClasses<tail>::Result
Result;
static inline void Fn(void)
{
Result::RegisterDerivedClass();
ReDerivedClasses<typelist<head>, idx - 1>::Fn();
}
};
这里的关键字 'inline
' 似乎至关重要:它指示编译器在调用处注入代码。
最后,让我们定义别名。
typedef RegDerivedClasses<typelist_4(derived0> RegClassStruct;
现在,您注册类的唯一要做的事情是在应用程序中编写:
RecClassStruct::Fn();
建议将所有与类型列表和 struct RegDerivedClasses
相关的代码保存在同一个文件中,该文件还包含基类、派生类和工厂类的代码。
自带类
假设您想添加自己的类 CDerived4
到工厂。现在您有五个类需要注册,四个旧类和一个新类。
假设您可以访问定义 CDerived
的源文件,请按以下步骤进行:
编写新类 CDerived4
,包含必需的成员 CreateDerived()
和 RegisterDerivedClass()
以及其唯一标识符。如果下一个 TYPELIST
没有 #define
,请编写一个新的。
#define TYPELIST_5(T1, T2, T3, T4, T5)
TypeList<t1>
Change last typedef;
typedef RegDerivedClasses<typelist_5(derived0> RegClassStruct;
编译并运行。
当然,可能还需要修改可执行代码才能使用新添加的类,但这属于另一回事。
演示应用程序
源代码基本上已经解释完毕。
本文配套的演示应用程序使用 Microsoft VC++ 2010 Express 编译。(您可以从 Microsoft 网站免费下载 VC++ 2010 Express。)
该应用程序将四个类 CDerived0
到 CDerived_3
注册到工厂,并使用工厂创建类实例,然后将类名称写入控制台。
我还包含了 Doc 文件夹,其中包含 Doxygen 生成的 HTML 文档文件。
在您的浏览器或任何 HTML 阅读器中打开 'index.html' 文件,然后导航到您想要的任何位置。
文学
- Eric Gamma 等人,《设计模式:可复用面向对象软件基础》,Addison-Wesley
- Andrei Alexandrescu,《现代 C++ 设计:泛型编程与设计模式应用》,Addison-Wesley
历史
- 2010 年 6 月 8 日:初始版本