1. 程式人生 > >C++模板實現的通用工廠方法模式

C++模板實現的通用工廠方法模式

1.工廠方法(Factory Method)模式

工廠方法模式的意義是定義一個建立產品物件的工廠介面,將實際建立工作推遲到子類當中。核心工廠類不再負責產品的建立,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的介面,這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色的情況下引進新的產品。


工廠方法模式結構示意圖

工廠方法模式是簡單工廠模式的衍生,解決了許多簡單工廠模式的問題。首先完全實現‘開-閉 原則’,實現了可擴充套件。其次更復雜的層次結構,可以應用於產品結果複雜的場合。

工廠方法模式很好用,但在程式碼層面上,為每一個產品都編寫一個對應的工廠,這無疑很麻煩,而且不同的Product

要寫不同Creator。想想你在寫一個GUI庫,基本控制元件為Widget類,由其派生下了TextBoxButtonLabel等幾十個控制元件,那你需要為這幾十個控制元件分別編寫對應的ConcreteCreator。天,這太麻煩了吧!而如果你還要寫一個RPG小遊戲,需要從Person類派生下如NPCHeroEmenyAlly等又是十幾個類,而且還要編寫一個對應於PersonCreator....

2.模板實現工廠方法模式

   面對這種型別造成的多型,C++模板就要出場了。現在情況是這樣的,ProductConcreteProduct都已經寫好,我們要為其編寫對應的工廠類。可以看出來,

Product對應對CreatorConcreteProduct對應於ConcreteCreator。也就是說,如果想編寫通用的ConcreteCreator,就得將ConcreteProduct抽象,想編寫通用的Creator,就得將Product抽象。而我們最後會將這2者都抽象了。

   首先編寫通用的ConcreteCreator

  1. // 具體工廠類
  2. // Product為抽象產品,ConcreteProduct為具體產品
  3. template<typename Product, typename ConcreteProduct>  
  4. class ConcreteCreator  
  5. {  
  6. public:  
  7.     // 生產一個具體產品
  8.     static Product* createProduct()  
  9.     {  
  10.         returnnew ConcreteProduct();  
  11.     }  
  12. };  

注意createProduct()是靜態的,是因為該函式要被儲存起來的,靜態函式只需儲存其函式指標即可,而普通的成員函式還要額外儲存一個物件指標,這無疑更麻煩了。 也就是我們不需要這樣用: 

  1. ConcreteCreator<Product, ConcreteProduct> concreteCreator;     
  2. Product* p = concreteCreator.createProduct();  

只需要這樣寫:

  1. Product* p = ConcreteCreator<Product, ConcreteProduct>::createProduct();  

   下面是Creator的實現

  1. // 抽象工廠
  2. // Product為抽象產品
  3. template<typename Product>  
  4. class Creator  
  5. {  
  6.     // 單例實現
  7. public:  
  8.     static Creator& Instance()  
  9.     {  
  10.         static Creator<Product> instance;  
  11.         return instance;  
  12.     }  
  13. private:  
  14.     Creator() {}  
  15.     ~Creator() {}  
  16.     Creator(Creator&);  
  17.     // 對外介面
  18. public:  
  19.     typedef Product* (*CreateProductDelegate)( ); // 生產產品的函式委託
  20.     typedef std::map<std::string, CreateProductDelegate> MapRegisterCreatorItem;  
  21.     // 根據具體產品生成具體工廠
  22.     // 並將其註冊進抽象工廠
  23.     // ConcreteProduct為具體產品
  24.     template<typename ConcreteProduct>  
  25.     void registerCreator(const std::string& _type)  
  26.     {  
  27.         mConcreteCreators[_type] = ConcreteCreator<Product, ConcreteProduct>::createProduct;  
  28.     }  
  29.     // 刪除所有具體工廠
  30.     void unregisterAllCreators()  
  31.     {  
  32.         mConcreteCreators.clear();  
  33.     }  
  34.     // 生產型別為_type的產品
  35.     // 失敗返回0
  36.     Product* createProduct(const std::string& _type)  
  37.     {  
  38.         MapRegisterCreatorItem::iterator type = mConcreteCreators.find(_type);  
  39.         if (type != mConcreteCreators.end())  
  40.         {  
  41.             CreateProductDelegate create = type->second;  
  42.             if (create != 0)  
  43.                 return create();  
  44.         }  
  45.         return 0;  
  46.     }  
  47. private:  
  48.     MapRegisterCreatorItem mConcreteCreators; // 儲存所有註冊過的具體工廠
  49. };  

下面來簡單解釋一下上面的程式碼。

首先Creator實現了單例模式,這只是為了方便使用,你也可以不實現為單例。

Creator裡面儲存了所有註冊過的具體工廠,具體工廠在註冊時被構建,每個具體工廠對應一個名字(string型別)。

createProduct即為工廠方法,外部通過此介面建立產品,建立時僅需提供具體工廠的名字即可。

我們以RPG遊戲那個作為例子。要建立一個PersonCreator,可以這樣寫

  1. typedef Creator<Person> PersonCreator;  
  2. PersonCreator& factory = PersonCreator::Instance();  
  3. factory.registerCreator<Person>("Person");  
  4. factory.registerCreator<Hero>("Hero");  
  5. factory.registerCreator<NPC>("NPC");  

這樣即完成了註冊過程,你可以繼續為其他型別註冊具體工廠。

要建立一個NPC,可以這樣寫

  1. Person* npc = factory.createProduct("NPC");  

3.優缺點

此方法的優點很明顯,就是用起來很方便。由於Creator支援不同型別,你不要為不同的產品編寫不同的Creator。而且其提供了註冊具體工廠的方法,你僅需要寫一行程式碼即可為具體產品生成具體的工廠,而不需要編寫專門的類。

而且其註冊過程是可以在執行時修改的,也就是說你可以在執行時動態地註冊或反註冊具體工廠,這樣靈活性就很大了。

缺點的話就是不支援建構函式引數,也就是說在建立產品時不支援引數。當然可以為不同數量引數編寫具體的模板Creator。但其實個人並不提倡提供無參建構函式。我認為像這類產品類,都應該提供一個無參的建構函式版本,然後提供getset函式來修改內部成員變數。