工廠模式:
一、引子
話說十年前,有一個(gè)用戶,他家有三輛汽車——Benz奔馳、Bmw寶馬、Audi奧迪,還雇了司機(jī)為他開車。不過,用戶坐車時(shí)總是怪怪的:上 Benz車后跟司機(jī)說“開奔馳車!”,
坐上Bmw后他說“開寶馬車!”,坐上Audi說“開奧迪車!”。你一定說:這人有病!直接說開車不就行了?! 而當(dāng)把這個(gè)用戶的行為放到我們程序設(shè)計(jì)中來時(shí),會(huì)發(fā)現(xiàn)這是一個(gè)
普遍存在的現(xiàn)象。幸運(yùn)的是,這種有病的現(xiàn)象在OO(面向?qū)ο螅┱Z言中可以避免了。下面就以Java語言為基礎(chǔ)來引入我們本文的主題:工廠模式。
二、工廠模式分為:
工廠模式主要是為創(chuàng)建對(duì)象提供過渡接口,以便將創(chuàng)建對(duì)象的具體過程屏蔽隔離起來,達(dá)到提高靈活性的目的。
1、簡單工廠模式(Simple Factory) :又稱靜態(tài)工廠方法模式,重命名上就可以看出這個(gè)模式一定很簡單。它存在的目的很簡單:定義一個(gè)用于創(chuàng)建對(duì)象的接口。 但不利于產(chǎn)生系列產(chǎn)品。
在簡單工廠模式中,一個(gè)工廠類處于對(duì)產(chǎn)品類實(shí)例化調(diào)用的中心位置上,它決定那一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化, 如同一個(gè)交通警察站在來往的車輛流中,決定放行那一個(gè)方向的車輛向那一個(gè)方向流動(dòng)一樣。
Simple Factory 模式角色組成:
1) 工廠類角色:這是本模式的核心,含有一定的商業(yè)邏輯和判斷邏輯。在java中它往往由一個(gè)具體類實(shí)現(xiàn)。
2) 抽象產(chǎn)品角色:它一般是具體產(chǎn)品繼承的父類或者實(shí)現(xiàn)的接口。在java中由接口或者抽象類來實(shí)現(xiàn)。
3) 具體產(chǎn)品角色:工廠類所創(chuàng)建的對(duì)象就是此角色的實(shí)例。在java中由一個(gè)具體類實(shí)現(xiàn)。
下面我們從開閉原則(對(duì)擴(kuò)展開放;對(duì)修改封閉)上來分析下簡單工廠模式:
當(dāng)用戶增加了一輛車的時(shí)候,只要符合抽象產(chǎn)品制定的合同,那么只要通知工廠 類知道就可以被客戶使用了。所以對(duì)產(chǎn)品部分來說,它是符合開閉原則的;但是工廠部分好像不太理想,
因?yàn)槊吭黾右惠v車,都要在工廠類中增加相應(yīng)的業(yè)務(wù)邏輯或 者判斷邏輯,這顯然是違背開閉原則的。可想而知對(duì)于新產(chǎn)品的加入,工廠類是很被動(dòng)的。對(duì)于這樣的工廠類(在我們的例子
中是為CarFactory),我們稱它為全能類或者上帝類。
我們舉的例子是最簡單的情況,而在實(shí)際應(yīng)用中,很可能產(chǎn)品是一個(gè)多層次的樹狀結(jié)構(gòu)。由于簡單工廠模式中只有一個(gè)工廠類來對(duì)應(yīng)這些產(chǎn)品,所以這可能會(huì)把我們的上帝累壞了,也累
壞了我們這些程序員,于是工廠方法模式作為救世主出現(xiàn)了;
2、工廠方法模式(Factory Method):又稱為多形性工廠,工廠方法模式去掉了簡單工廠模式中工廠方法的靜態(tài)屬性,使得它可以被子類繼承。這樣在簡單工廠模式里集中在工廠方法上的壓力可以由工廠方法模
式里不同的工廠子類來分擔(dān)。工廠方法模式是簡單工廠模式的進(jìn)一步抽象化和推廣,工廠方法模式里不再只由一個(gè)工廠類決定那一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化,這個(gè)決定被交給抽象工廠的子類去做。
Factory Method模式角色組成:
1)抽象工廠角色: 這是工廠方法模式的核心,它與應(yīng)用程序無關(guān)。是具體工廠角色必須實(shí)現(xiàn)的接口或者必須繼承的父類。在java中它由抽象類或者接口來實(shí)現(xiàn)。
2)具體工廠角色:它含有和具體業(yè)務(wù)邏輯有關(guān)的代碼。由應(yīng)用程序調(diào)用以創(chuàng)建對(duì)應(yīng)的具體產(chǎn)品的對(duì)象。
3)抽象產(chǎn)品角色:它是具體產(chǎn)品繼承的父類或者是實(shí)現(xiàn)的接口。在java中一般有抽象類或者接口來實(shí)現(xiàn)。
4)具體產(chǎn)品角色:具體工廠角色所創(chuàng)建的對(duì)象就是此角色的實(shí)例。在java中由具體的類來實(shí)現(xiàn)。
工廠方法模式使用繼承自抽象工廠角色的多個(gè)子類來代替簡單工廠模式中的“上帝類”。正如上面所說,這樣便分擔(dān)了對(duì)象承受的壓力;而且這樣使得結(jié)構(gòu)變得靈活 起來——當(dāng)有新的產(chǎn)品(
即用戶的汽車)產(chǎn)生時(shí),只要按照抽象產(chǎn)品角色、抽象工廠角色提供的合同來生成,那么就可以被客戶使用,而不必去修改任何已有的代 碼。可以看出工廠角色的結(jié)構(gòu)也是符合開閉原則的!
可以看出工廠方法的加入,使得對(duì)象的數(shù)量成倍增長。當(dāng)產(chǎn)品種類非常多時(shí),會(huì)出現(xiàn)大量的與之對(duì)應(yīng)的工廠對(duì)象,這不是我們所希望的。因?yàn)槿绻荒鼙苊膺@種情 況,可以考慮使用簡單工廠
模式與工廠方法模式相結(jié)合的方式來減少工廠類:即對(duì)于產(chǎn)品樹上類似的種類(一般是樹的葉子中互為兄弟的)使用簡單工廠模式來實(shí)現(xiàn)。
3、簡單工廠模式與工廠方法模式小結(jié):
工廠方法模式仿佛已經(jīng)很完美的對(duì)對(duì)象的創(chuàng)建進(jìn)行了包裝,使得客戶程序中僅僅處理抽象產(chǎn)品角色提供的接口。那我們是否一定要在代碼中遍布工廠呢?大可不必。也許在下面情況下你可以
考慮使用工廠方法模式:
1)當(dāng)客戶程序不需要知道要使用對(duì)象的創(chuàng)建過程。
2)客戶程序使用的對(duì)象存在變動(dòng)的可能,或者根本就不知道使用哪一個(gè)具體的對(duì)象。
簡單工廠模式與工廠方法模式真正的避免了代碼的改動(dòng)了?沒有。在簡單工廠模式中,新產(chǎn)品的加入要修改工廠角色中的判斷語句;而在工廠方法模式中,要么將判斷邏輯留在抽象工廠角色
中,要么在客戶程序中將具體工廠角色寫死(就象上面的例子一樣)。而且產(chǎn)品對(duì)象創(chuàng)建條件的改變必然會(huì)引起工廠角色的修改。 面對(duì)這種情況,Java的反射機(jī)制與配置文件的巧妙結(jié)合突破
了限制——這在Spring中完美的體現(xiàn)了出來。
工廠方法模式和簡單工廠模式在定義上的不同是很明顯的。工廠方法模式的核心是一個(gè)抽象工廠類,而不像簡單工廠模式, 把核心放在一個(gè)實(shí)類上。工廠方法模式可以允許很多實(shí)的工廠類從抽象
工廠類繼承下來, 從而可以在實(shí)際上成為多個(gè)簡單工廠模式的綜合,從而推廣了簡單工廠模式。反過來講,簡單工廠模式是由工廠方法模式退化而來。設(shè)想如果我們非常確定一個(gè)系統(tǒng)只需要一個(gè)實(shí)
的工廠類, 那么就不妨把抽象工廠類合并到實(shí)的工廠類中去。而這樣一來,我們就退化到簡單工廠模式了。
4、抽象工廠模式(Abstract Factory):又稱為工具箱,產(chǎn)生產(chǎn)品族,但不利于產(chǎn)生新的產(chǎn)品
什么是產(chǎn)品族: 位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中,功能相關(guān)聯(lián)的產(chǎn)品組成的家族。還是讓我們用一個(gè)例子來形象地說明一下吧。回到抽象工廠模式的話題上。可以說,抽象工廠模式和工廠方法模式的
區(qū)別就在于需要?jiǎng)?chuàng)建對(duì)象的復(fù)雜程度上。而且抽象工廠模式是三個(gè)里面最為抽象、最具一般性的。 抽象工廠模式的用意為:給客戶端提供一個(gè)接口,可以創(chuàng)建多個(gè)產(chǎn)品族中的產(chǎn)品對(duì)象。
使用抽象工廠模式還要滿足一下條件:
1)系統(tǒng)中有多個(gè)產(chǎn)品族,而系統(tǒng)一次只可能消費(fèi)其中一族產(chǎn)品。
2)同屬于同一個(gè)產(chǎn)品族的產(chǎn)品以其使用。
Abstract Factory模式角色組成(和工廠方法的如出一轍):
1)抽象工廠角色: 這是工廠方法模式的核心,它與應(yīng)用程序無關(guān)。是具體工廠角色必須實(shí)現(xiàn)的接口或者必須繼承的父類。在java中它由抽象類或者接口來實(shí)現(xiàn)。
2)具體工廠角色:它含有和具體業(yè)務(wù)邏輯有關(guān)的代碼。由應(yīng)用程序調(diào)用以創(chuàng)建對(duì)應(yīng)的具體產(chǎn)品的對(duì)象。在java中它由具體的類來實(shí)現(xiàn)。
3)抽象產(chǎn)品角色:它是具體產(chǎn)品繼承的父類或者是實(shí)現(xiàn)的接口。在java中一般有抽象類或者接口來實(shí)現(xiàn)。
4)具體產(chǎn)品角色:具體工廠角色所創(chuàng)建的對(duì)象就是此角色的實(shí)例。在java中由具體的類來實(shí)現(xiàn)。
在抽象工廠模式中,抽象產(chǎn)品 (AbstractProduct) 可能是一個(gè)或多個(gè),從而構(gòu)成一個(gè)或多個(gè)產(chǎn)品族(Product Family)。 在只有一個(gè)產(chǎn)品族的情況下,抽象工廠模式實(shí)際上退化到工廠方法模式。
三、總結(jié)
三種模式從上到下逐步抽象,并且更具一般性。GOF在《設(shè)計(jì)模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)。將簡單工廠模式
(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。
(1)簡單工廠模式是由一個(gè)具體的類去創(chuàng)建其他類的實(shí)例,父類是相同的,父類是具體的。
(2)工廠方法模式是有一個(gè)抽象的父類定義公共接口,子類負(fù)責(zé)生成具體的對(duì)象,這樣做的目的是將類的實(shí)例化操作延遲到子類中完成。
(3)抽象工廠模式提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無須指定他們具體的類。它針對(duì)的是有多個(gè)產(chǎn)品的等級(jí)結(jié)構(gòu)。而工廠方法模式針對(duì)的是一個(gè)產(chǎn)品的等級(jí)結(jié)構(gòu)。
四、個(gè)人總結(jié)
總結(jié)之前,先說兩個(gè)概念,請(qǐng)確保理解,如下:
1. 產(chǎn)品:具體到某一實(shí)例,如:寶馬,奔馳,J20,F(xiàn)35等;
2. 產(chǎn)品族:具體到某一類產(chǎn)品,如:汽車(Car),飛機(jī)(Plane)等;
簡單工廠模式(Simple Factory):適用于同一產(chǎn)品族,固定產(chǎn)品種類的情況,優(yōu)點(diǎn):代碼簡單,缺點(diǎn):不利于增加新產(chǎn)品,需修改原有工廠代碼;
工廠方法模式(Factory Method):適用于同一產(chǎn)品族,可變產(chǎn)品種類的情況,優(yōu)點(diǎn):代碼簡單,利于增加新產(chǎn)品,無需修改原有工廠代碼,缺點(diǎn):子工廠類遍布各處,子工廠類易泛濫;
抽象工廠模式(Abstract Factory):適用于不同產(chǎn)品族情況,優(yōu)點(diǎn):可生產(chǎn)不同產(chǎn)品族的產(chǎn)品,缺點(diǎn):不利于增加新產(chǎn)品,即使在工廠類:ProductFactoryByFactory中,使用了調(diào)用產(chǎn)品
Factory創(chuàng)建對(duì)象,在增加新產(chǎn)品時(shí),依然違背了不修改原有工廠代碼的原則;
五、延伸閱讀
Java的開閉原則:一個(gè)軟件系統(tǒng)應(yīng)該對(duì)開展時(shí)開放的,對(duì)修改時(shí)關(guān)閉的(Software entity should be open for extension, but closed for modification);在設(shè)計(jì)一個(gè)模塊的時(shí)候,應(yīng)該
可以使這個(gè)模塊可以在不被修改的前提下被擴(kuò)張。換言之,應(yīng)該可以在不必修改源代碼的情況下改變這個(gè)模塊的行為。
聽上去很矛盾,但是通過一些使用一些設(shè)計(jì)模式就可以輕松的做到,如 適配器模式(adapter)等等。滿足了OCP(Open-Closed Principle)原則可以給軟件系統(tǒng)帶來兩個(gè)無法比擬的設(shè)計(jì)目標(biāo):
1.擴(kuò)展性。通過擴(kuò)展已有的軟件系統(tǒng),可以提供新的行為,以滿足對(duì)軟件的新需求。
2.穩(wěn)定性。已有的軟件模塊,特別是最重要的抽象層模塊不能被修改,這就是變化中的軟件系統(tǒng)有一定的穩(wěn)定性和可延續(xù)性。
3.維護(hù)性大大提高。
怎么才能實(shí)現(xiàn)“開-閉”原則:
抽象化是關(guān)鍵
解決問題的關(guān)鍵在于抽象化。在像java這樣的面向?qū)ο蟮恼Z言中,可以給系統(tǒng)定義出一個(gè)一勞永逸,不再更改的抽象設(shè)計(jì),此設(shè)計(jì)允許有無窮無盡的實(shí)現(xiàn),并且互不影響。在java語言中,給出一個(gè)
抽象的類或者接口,規(guī)定出具體方法必須提供的方法特征作為系統(tǒng)設(shè)計(jì)的抽象層。這個(gè)抽象層預(yù)見了所有的可能擴(kuò)展,因此,在任何擴(kuò)展情況下都不會(huì)改變。這就使得系統(tǒng)的抽象層不需要修改,從
而滿足了“開-閉”原則的第二條:對(duì)修改是關(guān)閉的。
同時(shí),由于從抽象層導(dǎo)出一個(gè)或多個(gè)新的具體類來實(shí)現(xiàn)抽象類或者接口就可以改變系統(tǒng)的行為,因此系統(tǒng)的設(shè)計(jì)對(duì)擴(kuò)展時(shí)開放的,這就滿足了“開-閉”原則的第一條:對(duì)可變性的封裝原則 。如何
設(shè)計(jì)好抽象層的類或者接口,我們必須對(duì)系統(tǒng)的可變性進(jìn)行封裝。這就是所謂的”對(duì)可變性的封裝原則"(Principle of Encapsulation of Variation short for EVP)對(duì)可變性的封裝原則 講的是找到
一個(gè)系統(tǒng)的可變因素,將其封裝起來。
“對(duì)可變性的封裝原則”意味著兩點(diǎn):
1.一種可變性不應(yīng)散落在很多角落,而應(yīng)當(dāng)被封裝到一個(gè)對(duì)象里面。
2.一種可變性不應(yīng)當(dāng)與另一種可變性混合在一起。