C++ 可配置的类工厂
项目中常用到工厂模式,工厂模式可以把创建对象的具体细节封装到Create函数中,减少重复代码,增强可读和可维护性。传统的工厂实现如下:
1 class Widget 2 { 3 public: 4 virtual int Init() 5 { 6 printf("Widget Init"); 7 return 0; 8 } 9 }; 10 11 class WidgetA : public Widget 12 { 13 public: 14 virtual int Init() 15 { 16 printf("WidgetA Init"); 17 return 0; 18 } 19 }; 20 21 class WidgetB : public Widget 22 { 23 public: 24 virtual int Init() 25 { 26 printf("WidgetB Init"); 27 return 0; 28 } 29 }; 30 31 32 class IWidgetFactory 33 { 34 public: 35 virtual Widget *CreateWidget() = 0; 36 }; 37 38 class WidgetFactoryA : public IWidgetFactory 39 { 40 public: 41 virtual Widget *CreateWidget() 42 { 43 Widget *p = new WidgetA(); 44 p->Init(); 45 return p; 46 } 47 }; 48 49 class WidgetFactoryB : public IWidgetFactory 50 { 51 public: 52 virtual Widget *CreateWidget() 53 { 54 Widget *p = new WidgetB(); 55 p->Init(); 56 return p; 57 } 58 }; 59 60 61 int main() 62 { 63 IWidgetFactory *factoryA = new WidgetFactoryA(); 64 Widget *widgetA = factoryA->CreateWidget(); 65 IWidgetFactory *factoryB = new WidgetFactoryB(); 66 Widget *widgetB = factoryB->CreateWidget(); 67 68 return 0; 69 }
假设有类WidgetA,WidgetB继承自Widget,我们可以创建WidgetFactoryA和WidgetFactoryB,根据需要用factoryA对象或factoryB对象创建对应的对象。这样的方式可以满足大多数的需求。
现在假如有这样一种需求,我们需要根据配表来生成相应的对象。比如配表中配了值1,希望生成WidgetA,值2,希望生成WidgetB。此时如果还是上述的方法,可能我们只能判断值如果为1,就用factoryA,如果为2则用factoryB。如果有WidgetA-WidgetZ,我们肯定不希望一个个用ifelse做判断。
因此这里建立一个从type值到对象的工厂映射。只要事先注册好,就可以直接从配表读取数据,并根据type值直接创建对应的对象类型。
1 class WidgetFactoryImplBase; 2 class WidgetFactory 3 { 4 public: 5 typedef std::map<int, WidgetFactoryImplBase*> FactoryImplMap; 6 static WidgetFactory &Instance() 7 { 8 static WidgetFactory factory; 9 return factory; 10 } 11 12 void RegisterFactoryImpl(int type, WidgetFactoryImplBase *impl) 13 { 14 factory_impl_map_.insert(std::make_pair(type, impl)); 15 } 16 Widget *CreateWidget(int type); 17 private: 18 FactoryImplMap factory_impl_map_; 19 }; 20 21 class WidgetFactoryImplBase 22 { 23 public: 24 WidgetFactoryImplBase(int type) 25 { 26 WidgetFactory::Instance().RegisterFactoryImpl(type, this); 27 } 28 ~WidgetFactoryImplBase() 29 {} 30 virtual Widget *CreateWidget() = 0; 31 }; 32 33 template<int type, class WidgetType> 34 class WidgetFactoryImpl : WidgetFactoryImplBase 35 { 36 public: 37 WidgetFactoryImpl() : WidgetFactoryImplBase(type) 38 {} 39 ~WidgetFactoryImpl() 40 {} 41 virtual Widget *CreateWidget() 42 { 43 WidgetType *p = new WidgetType(); 44 p->Init(); 45 return p; 46 } 47 }; 48 49 Widget *WidgetFactory::CreateWidget(int type) 50 { 51 auto it = factory_impl_map_.find(type); 52 if (it == factory_impl_map_.end()) return NULL; 53 return it->second->CreateWidget(); 54 } 55 56 #define DECLARE_WIDGET(type, WidgetType) \ 57 static WidgetFactoryImpl<type, WidgetType> o_WidgetFactory_##type 58 59 DECLARE_WIDGET(0, Widget); 60 DECLARE_WIDGET(1, WidgetA); 61 DECLARE_WIDGET(2, WidgetB); 62 63 int main() 64 { 65 WidgetFactory::Instance().CreateWidget(1); 66 WidgetFactory::Instance().CreateWidget(2); 67 return 0; 68 }
由于工厂的Create函数大同小异,首先用模板类来定义特定值对应特定对象的工厂,如果WidgetC的创建过程和一般的不一致,再创建特化类,就省去了对每个对象类写工厂类的过程。然后将这些工厂在构造时自动注册到一个总的WidgetFactory中。真正创建时只需要调用总工厂的Create函数,传入配表等传入的type值,即可创建对应的对象。
注意这里用了一个DECLARE_WIDGET宏,来绑定type与对应的对象类型。从而将对应的创建工厂注册到总工厂中。
此方法的逻辑简单,也很好理解,在最近的游戏活动功能中,获得了非常好的效果。由于活动的类型多达几十种,为每一种活动写工厂类和根据配表值做判断会非常繁琐,也容易出错,利用了这样的工厂注册方法后,新加一个活动类型只要加一行注册代码即可搞定,且不会出错。这里把工厂注册机制分享出来,希望对大家有所帮助。