【C/C++開發】C++ 可配置的類工廠
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與對應的物件型別。從而將對應的建立工廠註冊到總工廠中。
此方法的邏輯簡單,也很好理解,在最近的遊戲活動功能中,獲得了非常好的效果。由於活動的型別多達幾十種,為每一種活動寫工廠類和根據配表值做判斷會非常繁瑣,也容易出錯,利用了這樣的工廠註冊方法後,新加一個活動型別只要加一行註冊程式碼即可搞定,且不會出錯。這裡把工廠註冊機制分享出來,希望對大家有所幫助。