程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Object Factories(對象工廠),objectfactories

Object Factories(對象工廠),objectfactories

編輯:C++入門知識

Object Factories(對象工廠),objectfactories


1,為什麼需要對象工廠?

 我們創建一個對象時,必須給出具體型別,new A也好,new B也罷。A,B都是對象的型別。我們向來反對寫hardcode,但卻在創建對象時必須hardcode。

 如果需要根據用戶的輸入信息,或是網絡反饋信息,或是文本文件信息來創建對象時,應該怎麼辦呢?

 最初我們想法可能是這樣的,偽代碼

switch(Info)
{
  case a: return new A;
  case b: return new B;
  ...
  default:...
}

這就是大家都熟悉簡單工廠模式,他雖然簡單,但蘊含一個基本原則:根據value(a,b...)查找type(A,B...),利用type創建value(A*,B*...)

利用對象工廠,我們創建對象時得到了解脫,無需寫hardcode,不必給出類型,我們可以提供數字,字符串,以及特定格式信息來創建一個對象。

好的,這就是對象工廠存在的意義!

2.簡單工廠不夠完美嗎?

是的,不夠完美。理由有三:

  • 它使用了switch語句,因而帶有switch語句相應缺點,這是面向對象極力消除的東西(switch本身就是一種hardcode,所以面向對象:多態)
  • 工廠類需要收集其生產的所有類型信息,編譯依存性很強哦
  • 想要添加一個新類型,需要修改工廠代碼,而且通常不只一處。(引入頭文件,添加常量,修改switch等)

3.我們的目標是什麼?

  • 工廠不使用switch語句
  • 工廠可伸縮,可以動態添加或刪除所生產的產品
  • 可以對工廠提供構造對象的原料,且數量可變
  • 泛化工廠,可以無需編碼支持不同類型抽象產品
  • 減少內存管理復雜度
  • 盡量做到類型安全

4.看看實現,這才是重點:(在我不斷完善實現的過程中,我發現自己是為了模式而模式,所以以下代碼僅供參考)

 1 struct package
 2 {
 3     void * funcSet;
 4     void * func;
 5     size_t index;
 6     string sig;
 7 };
 8  
 9 template <typename ...> class TypeList {};
10  
11 template <typename AbstractProduct ,typename IdentifierType = string> class FactoryImpl
12 {
13 public:
14     template <typename... Arg> bool Register(const IdentifierType& id,const function<unique_ptr<AbstractProduct>(Arg...)>& creator)
15     {
16  
17         static vector<function<unique_ptr<AbstractProduct>(Arg...)>> vf;
18         typename AssocMap::const_iterator i =associations_.find(id);
19         if(i!= associations_.end()) return false;
20         vf.push_back(creator);
21         return associations_.insert(typename AssocMap::value_type(id,package {&vf,&vf.back(),vf.size()-1,string(typeid(TypeList<Arg...>).name())})).second;
22  
23     }
24  
25     template <typename ... Arg >
26     bool UnRegister(const IdentifierType& id)
27     {
28         typename AssocMap::const_iterator i =associations_.find(id);
29         if(i != associations_.end())
30         {
31             assert(
32                 ((i->second).sig).compare(typeid(TypeList<Arg...>).name())==0
33             );
34             auto vf=static_cast<vector<function<unique_ptr<AbstractProduct>(Arg...)>>*>((i->second).funcSet);
35             vf->erase(vf->begin()+(i->second).index);
36         }
37  
38         return associations_.erase(id)==1;
39     }
40  
41     template <typename... Arg> unique_ptr<AbstractProduct> Createobject(const IdentifierType& id,Arg&&... args)
42     {
43         typename AssocMap::const_iterator i =associations_.find(id);
44  
45         if(i != associations_.end())
46         {
47             assert(((i->second).sig).compare(typeid(TypeList<Arg...>).name())==0);
48             auto funp=static_cast<function<unique_ptr<AbstractProduct>(Arg...)>* >((i->second).func);
49             return (*funp)(std::forward<Arg>(args)...);
50         }
51         assert(false);
52     }
53  
54  
55 private:
56     typedef std::unordered_map<IdentifierType,package> AssocMap;
57     AssocMap associations_;
58  
59 };

 

 代碼釋疑:

  •  17行,在干什麼?是這樣的,客戶傳過來的function對象很可能在調用時已經釋放了,如果我們不保存一個function對象,僅僅將其function對象指針轉變為void*,而之後我們在把void*轉換為function對象指針,調用時如果function對象已經釋放了,那麼必然出現內存錯誤。
  •  恰恰因為17行原因,一切變得非常棘手。最難實現的是UnRegister函數,這也是這段代碼最大的敗筆。UnRegister強迫客戶顯式提供正確的型別,否則不能工作。為什麼一定要提供型別?因為型別擦除後,編譯器已無法知道它真的型別,需要明確進行強制轉換後,才能在vector中刪除function對象。不然,內存洩露直至進程退出。
  • 所有函數都加入assert,防止客戶出錯,提供不正確參數將導致進程終止。
  • 代碼中使用std::function和unique_ptr, std::function為不同形式的可調用體包裝為相同型別,並保持統一的調用。unique_ptr則是為了方便內存管理。
  • 1-7package結構體中 func指向用戶提供function對象,funcSet指向Register函數中static vector,index為其在func指針在funcSet中位置,sig可以理解為函數簽名

因為沒有在類模板使用可變參數,所以實現起來很是費勁,我現在倒覺得可以使用類模板帶可變參數實現。這樣類模板實例化的類,雖然只支持注冊和調用參數固定調用體,但具有編譯期類型檢查的好處,Register和UnRegister均很容易實現。用戶需要時,根據需要實例化模板即可。

template <typename AbstractProduct ,typename IdentifierType ,typename... Arg  > class Factory
{

public:
    bool Register(const IdentifierType& id, std::function<unique_ptr<AbstractProduct> (Arg...)> creator)
    {
        return associations_.insert(typename AssocMap::value_type(id,creator)).second;
    }
    bool UnRegister(const IdentifierType& id)
    {
        return associations_.erase(id)==1;
    }
    unique_ptr<AbstractProduct> Createobject(const IdentifierType& id,Arg&&... args)
    {
        typename AssocMap::const_iterator i =associations_.find(id);
        if(i != associations_.end())
        {
            return (i->second)(std::forward<Arg>(args)...);
        }
        assert(false);
    }

private:
    typedef std::unordered_map<IdentifierType,std::function<unique_ptr<AbstractProduct> (Arg...)> > AssocMap;
    AssocMap associations_;

};

需要注意一下,這代碼因為使用變長模板參數,所以無法使用默認模板參數。

 

使用第一種實現方式,你只需要一個對象,即可搞定所有需求(UnRegister比較鬧心)。具有運行期類型檢查。

使用第二種實現方式,你要在不同場合下,實例化出類,並且創建出對象只支持注冊某種調用格式調用體。而且具有編譯期類型檢查。

 

雖然在第一種方式上,下了很多功夫,但是我還是不推薦這種做法,除非真的有萬不得已需要,非要把代碼設計成這樣。

春節前,最後一篇文章,轉載請標明出處,謝謝。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved