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

对象工厂设计模式:使用 TypeLists 和模板元编程注册创建者函数

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2010 年 6 月 9 日

CPOL

4分钟阅读

viewsIcon

51545

downloadIcon

673

如何使用 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 来获取类类型。

工作原理

假设我们要注册四个类:CDerived0CDerived1CDerived2CDerived2 ,以及它们的类型标识符。

让我们定义 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' 文件,然后导航到您想要的任何位置。

文学

  1. Eric Gamma 等人,《设计模式:可复用面向对象软件基础》,Addison-Wesley
  2. Andrei Alexandrescu,《现代 C++ 设计:泛型编程与设计模式应用》,Addison-Wesley

历史

  • 2010 年 6 月 8 日:初始版本
© . All rights reserved.