C++設計模式之一 工廠模式(簡單工廠、工廠和抽象工廠)

工廠模式和抽象工廠模式區別:

1.標準工廠模式一般只有一個方法,創建一個產品;抽象工廠模式有多個方法,創建一系列產品;

2.目的不同:工廠模式着重在於怎麼創建產品,抽象工廠着重於創建哪些產品;

3.工廠模式使用的是類的繼承,抽象工廠使用的是對象的組合。


開始設計模式自然而然到提到幾個原則:I、開閉法則(OCP);II、里氏代換法則(LSP);III、依賴倒置法則(DIP);IV、接口隔離法則(ISP);V、合成/聚合複用原則(CARP);VI、迪米特法則(LoD),這幾個法則在呂震宇 老師的設計模式(二)設計模式(三)中有非常詳盡的闡述和深入淺出的舉例分析。有興趣的朋友打開鏈接看一下就可以了。


補充說明:
  • 我這裏所以代碼都是用VS2005的C++編譯器實現。所以不能保證在其他IDE中能順利編譯,但是我想如果你使用其他編譯器,也應該不會有太大問題,主要也應該是stdafx.h文件中包含的頭文件問題。
  • 裏面出行的結構圖都是用微軟的Visio2003 繪製,大家下載後可以直接用Visio打開。
  • 在 以後所有的模式例子中都有客戶程序,客戶程序這個角色不是模式本身的內容,它是模式之外的部分,但是正是這個客戶程序完成了對模式的使用,模式本身的結構 是講解的重點,但是客戶程序如何使用模式也是理解模式的一個重要方面,因此在我後續的介紹中都有客戶程序這個角色,並會說明究竟調用模式中的哪些角色完成 對模式的使用。

簡單工廠模式

生活例子
吃飯是人的基本需求,如果人類不需要吃飯,可能我們就能活得清閒許多,也就不需要像現在一樣沒日沒夜的工作,學習。我們學習是爲了找到更好的工作,好工作 爲了賺更多的錢,最終爲了吃飽飯,吃好飯。因此可以說吃飯是與人息息相關,下面就從吃飯的例子來引入工廠模式的學習。

   如果你想吃飯了,怎麼辦自己做嗎?自己做就相當於程序中直接使用new。當然是自己下個指令,別人來做更爽。那就把做飯的任務交給你的老婆吧,那麼她就是 一個做飯的工廠了,你告訴她要要吃紅燒肉,等會她就從廚房給你端出來一盤香噴噴的紅燒肉了,再來個清蒸魚吧,大魚大肉不能太多,那就再來個爆炒空心菜,最 後再來個西紅柿雞蛋湯。下圖 1) 就是這個問題的模型。
 
(圖1)
顯然到了這裏,你是Client,你老婆就是工廠,她擁有做紅燒肉的方法,做清蒸魚的方法,做爆炒空心菜、西紅柿雞蛋湯的方法,這些方法返回值就是食物 抽象。紅燒肉、清蒸魚、爆炒空心菜、西紅柿雞蛋湯就是食物的繼承類,到這裏你就可以大吃二喝了。簡單工廠模式也成型了。哈哈,娶一個手藝不錯的老婆還真 好,吃的好,吃的爽,又清閒。

   下面來看標準的簡單工廠模式的分析。 
意圖
把一系列擁有共同特徵的產品的創建封裝
結構圖
 
(圖2)
角色分析
產品基類: 工廠創建的所有產品的基類, 它負責描述所有實例所共有的公共接口。它用來作爲工廠方法的返回參數。
代碼實現:
        //---這時一個系列的產品基類
class Product
{
protected:
Product(void);
public:
virtual ~Product(void);
public:
virtual void Function() = 0;
};
//cpp
Product::Product(void)
{
}
Product::~Product(void)
{
}
具體產品類:產品1和產品2,這個角色實現了抽象產品角色所定義的接口。
代碼實現:
        //產品A
class ConcreteProductA:public Product
{
public:
ConcreteProductA(void);
public:
virtual ~ConcreteProductA(void);
public:
virtual void Function();
};
//cpp
ConcreteProductA::ConcreteProductA()
{
cout<<"創建 A 產品"<<endl;
}
ConcreteProductA::~ConcreteProductA()
{
cout<<"釋放 A 產品"<<endl;
}
void ConcreteProductA::Function()
{
cout<<"這是產品 A 具有的基本功能"<<endl;
}
//產品B與A類似不這裏不再給出,大家可以下載源碼

工廠類:負責具體產品的創建,有兩種方式實現產品的創建,I、創建不同的產品用不同的方法;II、創建不同產品用相同的方法,然後通過傳遞參數實現不同產品的創建。本實例中兩種模式都給出了,大家自行分析。 
//簡單工廠,此類不需要繼承,直接硬編碼實現生成的產品
class SimpleFactory
{
public:
SimpleFactory(){}
public:
~SimpleFactory(){}
public:
Product *CreateProduct(int ProuctType);
Product *CreateProductA();
Product *CreateProductB();
};
//CPP
Product * SimpleFactory::CreateProduct(int ProductType=0)
{
Product *p = 0;
switch(ProductType)
{
case 0:
p= new ConcreteProductA();
break;
case 1:
p= new ConcreteProductB();
break;
default:
p= new ConcreteProductA();
break;
}
return p;
}
Product *SimpleFactory::CreateProductA()
{
return new ConcreteProductA();
}
Product *SimpleFactory::CreateProductB()
{
return new ConcreteProductB();
}
客戶端程序:訪問的角色包括產品基類、工廠類。不直接訪問具體產品類。通過基類指針的多態實現產品功能的調用。
訪問描述:客戶程序通過調用工廠的方法返回抽象產品,然後執行產品的方法。
//調用代碼
SimpleFactory sf;
Product *p = sf.CreateProductA();
p->Function();
delete p;
p = sf.CreateProductB();
p->Function();
delete p;
優缺點說明
優點:1) 首先解決了代碼中大量New的問題。爲何要解決這個問題,好處的說明我想放到結尾總結中。
   2) 用工廠方法在一個類的內部創建對象通常比直接創建對象更靈活。 
缺點:對修改不封閉,新增加產品您要修改工廠。違法了鼎鼎大名的開閉法則(OCP)。
附加說明
  • 大家可以參看 呂震宇 老師的C#設計模式(四)參看這個模式的分析,裏面還給出了這個模式的兩個變體,實現比較簡單,有興趣的朋友可以自行用C++實現一下。
  • 產 品基類的代碼中構造函數我用了Protected,而沒有使用Public,主要是爲了體現編碼中的一個最小權限原則。說明此類不許用戶直接實例化。雖然 這裏使用了virtual void Function() = 0;編譯器也會控制不讓用戶直接實例化,不過我依然認爲使用私有化構造函數來保護類不直接實例化是一個良好的編程風格。


工廠方法模式

生活例子:
   人是最貪得無厭的動物,老婆手藝再好,總有不會做的菜,你想吃回鍋肉,怎麼辦,讓老婆學唄,於是就給她就新增了做回鍋肉的方法,以後你再想吃一個新菜,就 要給你老婆新加一個方法,顯然用老婆做菜的缺點也就暴露出來了,用程序設計的描述就是對修改永遠不能封閉。當然優點也是有的,你有了老婆這個工廠,這些菜 不用你自己做了,只要直接調用老婆這個工廠的方法就可以了。 

   面對上面對修改不能封閉的問題,有沒有好的解決方案嗎,如果你有錢,問題就迎刃而解了,把老婆抽象變成一個基類,你多娶幾個具體的老婆,分別有做魚 的,做青菜的,燉湯的老婆,如果你想吃一個新菜,就再新找個女人,從你的老婆基類繼承一下,讓她來做這個新菜。顯然多多的老婆這是所有男人的夢想,沒有辦 法,法律不允許,那麼咱們只是爲了做飯,老婆這個抽象類咱們不叫老婆了,叫做廚師吧,她的子類也自然而然的該叫做魚的廚師、燉湯的廚師了。現在來看這個模 式發生了變化,結構中多了一個廚師的抽象,抽象並不具體的加工產品了,至於是燉湯還是燉魚,是由這個抽象工廠的繼承子類來實現,現在的模式也就變成工廠方 法模式了,這個上面的結構圖1)就變成了下面的圖3的結構了。
 
   (圖3)

   現在再來分析現在的模式,顯然簡單工廠的缺陷解決了,新增加一個菜只需要新增加一個廚師就行了,原來的廚師還在做原來的工作,這樣你的設計就對修改封 閉了。你看把老婆解放出來,招聘大量的廚師到你家裏這個方案多麼的完美,你老婆也會愛死你了。當然前提就是你要有多多的錢噢,當然這裏的錢的多少在軟件領 域應該看你的客戶軟件投資方的要求。 
下面來一下標準的工廠模式的實現
意圖
  • 定義一個用戶創建對象的接口,讓子類決定實例化哪一個類。Factory Method使一個類的實例化延遲到其子類。
  • 上面是GOF關於此模式的意圖描述,我想補充的是您可以這樣理解:爲了改善簡單工廠對修改不能關閉的問題。
結構


圖4
角色分析
產品基類:同簡單工廠的產品基類,其實就是用和簡單工廠中的是同一個類,這裏並沒有重寫。
具體產品類:也是用的簡單工廠的具體產品類,爲了體現對修改的關閉這裏爲系統新添加了一個具體產品類,就是“新產品”,代碼中叫做“ConcreteProductANew”
工廠基類:定義了工廠創建產品的接口,但是沒有實現,具體創建工作由其繼承類實現。
代碼實例 
//工廠模式,此模式的工廠只定義加工產品的接口,具體生成交予其繼承類實現
//只有具體的繼承類才確定要加工何種產品
class Factory
{
public:
Factory(void);
public:
virtual ~Factory(void);
public:
virtual Product* CreateProduct(int ProductType = 0) =0;
};
//CPP
Factory::Factory(void)
{
}
Factory::~Factory(void)
{
}
具體工廠類:工廠基類的具體實現,由此類決定創建具體產品,這裏 ConcreteFactory1 對於與圖中的 工廠實現ConcreteFactory2 對於與圖中的新工廠
下面給出實現代碼
//工廠實現
class ConcreteFactory1:public Factory
{
public:
ConcreteFactory1();
public:
virtual ~ConcreteFactory1();
public :
Product* CreateProduct(int ProductType);
};
//新工廠,當要創建新類是實現此新工廠
class ConcreteFactory2:public Factory
{
public:
ConcreteFactory2();
public:
virtual ~ConcreteFactory2();
public :
Product* CreateProduct(int ProductType);
};
//CPP
ConcreteFactory1::ConcreteFactory1()
{
}
ConcreteFactory1::~ConcreteFactory1()
{
}
Product * ConcreteFactory1::CreateProduct(int ProductType = 0)
{
Product *p = 0;
switch(ProductType)
{
case 0:
p= new ConcreteProductA();
break;
case 1:
p= new ConcreteProductB();
break;
default:
p= new ConcreteProductA();
break;
}
return p;
}
ConcreteFactory2::ConcreteFactory2()
{
}
ConcreteFactory2::~ConcreteFactory2()
{
}
Product * ConcreteFactory2::CreateProduct(int ProductType = 0)
{
return new ConcreteProductANew();
}
客戶端調用:訪問角色(產品基類、工廠基類、工廠實現類)
調用描述:客戶程序通過工廠基類的方法調用工廠實現類用來創建所需要的具體產品。從而實現產品功能的訪問。
代碼實現
Factory*fct = new ConcreteFactory1();
Product *p = fct->CreateProduct(0);
p->Function();
delete p;
p = fct->CreateProduct(1);
p->Function();
delete p;
delete fct;
fct = new ConcreteFactory2();
p=fct->CreateProduct();
delete p;
delete fct;
優缺點分析
優點
  • 簡單工廠具有的優點
  • 解決了簡單工廠的修改不能關閉的問題。系統新增產品,新增一個產品工廠即可,對抽象工廠不受影響。
缺點:對於創建不同系列的產品無能爲力
適用性
  • 當一個類不知道它所必須創建的對象的類的時候。
  • 當一個類希望由它的子類來指定它所創建的對象的時候。
  • 當類將創建對象的職責委託給多個幫助子類中的某一個,並且你希望將哪一個幫助子類是代理者這一信息局部化的時候。
其他參考

抽象工廠模式

生活例子
    世事多變,隨着時間的推移,走過的地方越來越多,你天南海北的朋友也越來越多。你發現菜原來還分了許多菜系,魯菜、粵菜、湘菜等等,它們各有各的風味,同 樣是紅燒肉由不同菜系出來的味道也各不相同, 你招待不同的朋友要用不同的菜系,這下難辦了,你的廚師都是魯菜風味,怎麼辦,廣東的朋友來了吃不慣。現在我們再回到簡單工廠模式(就是老婆做菜的模 式),我們把紅燒肉再向下繼承,生成魯菜紅燒肉、粵菜紅燒肉、湘菜紅燒肉;清蒸魚向下繼承爲魯菜清蒸魚、粵菜清蒸魚、湘菜清蒸魚,其它也以此類推。我們也 修改一下老婆的這個類,不讓其返回食物基類,而是返回紅燒肉、清蒸魚、爆炒空心菜、西紅柿雞蛋湯這一層次,並把這些方法抽象化,作爲菜系工廠基類,然後再 從此基類繼承出,魯菜工廠、粵菜工廠、湘菜工廠等等,再由這些具體工廠實現創建具體菜的工作,哈哈你如果招待廣東朋友就用粵菜工廠,返回的就是一桌粵菜菜 系的紅燒肉、清蒸魚、空心菜和西紅柿雞蛋湯了,你的廣東朋友一定會吃的非常合乎胃口了。噢,非常好,你已經實現了抽象工廠模式了。結構模型圖也變成了下圖 6)的樣子了。

(圖6)
       現在可以看到,想新來做一個菜系,只需新聘請一個廚師就可以了,多麼完美,但是你先別高興太早,如果你想新增加一個菜就變得非常困難了。
意圖
提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。 
結構

角色分析
產品基類:這裏包含產品基類A和產品基類B,實際上在我的示例代碼中,這兩個產品都從共同的基類繼承而來,但是這個繼承關係卻是在這個模式之外的部分,而本身這個模式關心的是這兩個產品基類的差異部分。 
代碼實現:這裏的代碼就是借用的簡單工廠模式中具體產品類的代碼實現部分,爲了大家閱讀方便,下面重新給出一下。
        //產品A
class ConcreteProductA:public Product
{
public:
ConcreteProductA(void);
public:
virtual ~ConcreteProductA(void);
public:
virtual void Function();
};
//cpp
ConcreteProductA::ConcreteProductA()
{
cout<<"創建 A 產品"<<endl;
}
ConcreteProductA::~ConcreteProductA()
{
cout<<"釋放 A 產品"<<endl;
}
void ConcreteProductA::Function()
{
cout<<"這是產品 A 具有的基本功能"<<endl;
}
//產品B與A類似不這裏不再給出,大家可以下載源碼


具體產品
類:這裏的具體產品類是產品A1,A2,B1、B2等,
代碼實現:A1對應的實現就是“”
class ConcreteProductA1:public ConcreteProductA
{
public:
ConcreteProductA1(void);
public:
virtual ~ConcreteProductA1(void);
public:
virtual void Function();
};
//CPP
ConcreteProductA1::ConcreteProductA1()
{
cout<<"創建 A1 產品"<<endl;
}
ConcreteProductA1::~ConcreteProductA1()
{
cout<<"釋放 A1 產品"<<endl;
}
void ConcreteProductA1::Function()
{
cout<<"這時產品 A1 具有的基本功能"<<endl;
}

工廠抽象接口:定義了創建產品的接口,這裏返回參數是返回的產品A,產品B,而本身產品A和B的共同基類,小弟認爲正是這個特徵構成了抽象工廠和工廠模式的區別。
代碼實現
//抽象工廠模式
class AbstractFactory
{
public:
AbstractFactory();
public:
virtual ~AbstractFactory();
public:
virtual ConcreteProductA* CreateA() = 0;
virtual ConcreteProductB* CreateB() = 0;
};
//CPP
AbstractFactory::AbstractFactory()
{
}
AbstractFactory::~AbstractFactory()
{
}

具體工廠實現類:工廠1和工廠2。新增加系列,只需新實現一個工廠。
代碼實現: 工廠1的就是ConcreteAbsFactory1,工廠2的代碼類似,這裏沒有給出,可以在下載代碼中看到
////工廠1-----
class ConcreteAbsFactory1:public AbstractFactory
{
public:
ConcreteAbsFactory1();
public:
virtual ~ConcreteAbsFactory1();
public:
virtual ConcreteProductA* CreateA();
virtual ConcreteProductB* CreateB();
};
//CPP
ConcreteAbsFactory1::ConcreteAbsFactory1()
{
}
ConcreteAbsFactory1::~ConcreteAbsFactory1()
{
}
ConcreteProductA* ConcreteAbsFactory1::CreateA()
{
return new ConcreteProductA1();
}
ConcreteProductB * ConcreteAbsFactory1::CreateB()
{
return new ConcreteProductB1();
}

客戶端訪問: 訪問角色(產品基類、抽象工廠、具體工廠實現類)
訪問描述: 通過抽象工廠的指針訪問具體工廠實現來創建對應系列的產品,然後通過產品基類指針訪問產品功能。
調用代碼:
          AbstractFactory *absfct = new ConcreteAbsFactory1();
ConcreteProductA *cpa = absfct->CreateA();
cpa->Function();
delete cpa;
ConcreteProductB *cpb = absfct->CreateB();
cpb->Function();
delete cpb;
delete absfct;
absfct = new ConcreteAbsFactory2();
cpa = absfct->CreateA();
cpa->Function();
delete cpa;
cpb = absfct->CreateB();
cpb->Function();
delete cpb;

和工廠模式的分析比較
   現在可以和工廠模式對比一下,抽象工廠返回的接口不再是產品A和產品B的共同基類Product了,而是產品A、產品B基類(在工廠模式中它們爲具體實現 類,這裏變成了基類)了。此時工廠的抽象和簡單工廠中的工廠方法也很類似,就是這些特徵區使其別於工廠模式而變成抽象工廠模式了,因此抽象工廠解決的是創 建一系列有共同風格的產品(魯菜還是粵菜),而工廠方法模式解決的創建有共同特徵的一系列產品(紅燒肉、清蒸魚它們都是食物)。當然簡單工廠的缺陷在抽象 工廠中又再次出現了,我要新增加一個產品,工廠抽象接口就要改變了。因此抽象工廠並不比工廠模式完美,只不過是各自的適用領域不同而已。其實,這裏如果把 抽象工廠模式的接口返回產品A和產品B的共同基類(工廠模式返回的參數),你會發現,奇怪這個模式怎麼這麼眼熟,它不是恰恰退化成工廠模式了。
類模式與對象模式的區別討論:先看定義類“模式使用繼承關係,把對象的創建延遲的子類,對象模式把對象的創建延遲到另一個對象中”。 分析:首先它們創建對象都不是在基類中完成,都是在子類中實現,因此都符合類模式的概念;但是工廠模式的創建產品對象是在編譯期決定的,要調用某個工廠固 定的,而抽象工廠模式對產品的創建是在運行時動態決定的,只有到運行時才確定要調用那個工廠,調用工廠隨運行環境而改變。(這裏我一直很混亂,歡迎大家討 論)
適用性
  • 一個系統要獨立於它的產品的創建、組合和表示時
  • 一個系統要由多個 產品系列中的一個來配置時
  • 當你要強調一個系列相關的產品對象的設計以便進行聯合使用時
  • 當你提供一個產品類庫,而只想顯示它們的接口而不是實現時。
參考

總結

   工廠本質就是用工廠方法替代直接New來創建對象。這裏不是指的讓用戶重載一個新操作符號來進行創建對象的操作,而是說把New 操作封裝在一個方法中,等用戶需要創建對象時調用此方法而避免直接使用New而已。這樣做的目的就是之一就是封裝,避免代碼中大量New的運算符,這當然 不是主要目的,因爲這樣雖然New少了,CreateObject方法卻多了,但是如果產品類的構造函數變了,我想常用工廠模式的修改源代碼的工作應該簡 便許多吧,當然這算不上這個模式的好處,它的真正強大的功能其實在於適應變化,這也是整個設計模式最根本的目的;還有一點就是體現了抽象於實現的分離,當 然創建型模式都具有這個特點,工廠模式非常明顯吧了,把具體創建工作放置到工廠中,使客戶端程序更專注與業務邏輯的,這樣的代碼結構也更進行合理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章