工廠模式
1拓售、故事背景介紹
東漢《風(fēng)俗通》記錄了一則神話故事:“開天辟地兽肤, 未有人民折剃,女媧搏黃土做人”另假,講述的內(nèi)容就是大家非常熟悉的女媧造人的故事。開天辟地之初怕犁,大地上并沒有生物边篮,只有蒼茫大地,純粹而潔凈的自然環(huán)境奏甫,寂靜而又寂寞戈轿,于是女媧決定創(chuàng)造一個新物種 (即人類)來增加世界的繁榮,怎么制造呢阵子?
別忘了女媧是神仙思杯,沒有辦不到的事情,造人的過程是這樣的:首先款筑,女媧采集黃土捏成人的形狀智蝠,然后放到八卦爐中燒制,最后放置到大地上生長奈梳,工藝過程是沒有錯的杈湾,但是意外隨時都會發(fā)生:
第一次烤泥人,感覺應(yīng)該熟了攘须,往大地上一放漆撞,哇,沒烤熟于是一個白人誕生了S谥妗(這也是缺乏經(jīng)驗的最好證明)
第二次烤泥人浮驳,上一次沒烤熟,這次多烤一會兒捞魁,放到世間一看至会,嘿,熟過頭了谱俭,于是黑人誕生了奉件!
-
第三次烤泥人宵蛀,一邊燒制一邊察看,直到表皮微黃县貌,嘿术陶,真正好,于是黃色人種出現(xiàn)了煤痕!
這個造人過程是比較有意思的梧宫,是不是可以通過軟件開發(fā)來實現(xiàn)這個過程呢?古人云:“三人行摆碉,必有我?guī)熝伞碧料唬诿嫦驅(qū)ο蟮乃季S中,萬物皆對象兆解,是對象我們就可以通過軟件設(shè)計來實現(xiàn)馆铁。
首先我們對造人過程進行分析跑揉,該過程涉及三個對象:女媧锅睛、八卦爐、三種不同膚色的人历谍。女媧可以使用場景類Client(客戶现拒、使用者)來表示,八卦爐類似于一個工廠望侈,負(fù)責(zé)制造生產(chǎn)產(chǎn)品(即人類)印蔬,三種不同膚色的人,他們都是同一個接口下的不同實現(xiàn)類脱衙,都是人類侥猬,只是膚色、語言不同捐韩,對于八卦爐來說都是它生產(chǎn)出的產(chǎn)品退唠。
分析完畢,我們就可以畫出如圖所示的類圖荤胁。
類圖比較簡單瞧预,AbstractHumanFactory是一個抽象類,定義了一個八卦爐具有的整體功能仅政,HumanFactory為實現(xiàn)類垢油,完成具體的任務(wù)一創(chuàng)建人類;Human接口是人類的總稱圆丹,其三個實現(xiàn)類分別為三類人種滩愁;NvWa類是一個場景類,負(fù)責(zé)模擬這個場景辫封,執(zhí)行相關(guān)的任務(wù)硝枉。我們定義的每個人種都有兩個方法: getColor (獲得人的皮膚顏色)和talk (交談)玖瘸,其源代碼如下所示。
2檀咙、代碼示例如下
2.1 人類接口
//人類總稱
public interface Human {
//每個人種的皮膚都有相應(yīng)的顏色
public void getColor();
//人類會說話
public void talk();
}
2.2 人種類別
每個人種都至少具有兩個方法雅倒,黑色人種、黃色人種弧可、白色人種的代碼分別如下蔑匣。
2.2.1 黑色人種
//黑色人種
public c1ass B1ackHuman implements Human {
public void getColor() {
System.out.print1n("黑色人種的皮膚顏色是黑色的! ") ;
}
public void talk() {
System.out.println("黑人會說話,一般人聽不懂裁良。");
}
}
2.2.2 黃色人種
//黃色人種
public class Ye11 owHuman implements Human {
public void getColor() {
System .out.println("黃色人種的皮膚顏色是黃色的! ");
}
public void ta1k() {
system.out.print1n("黃色人種會說話,一般說的都是雙字節(jié)校套。");
}
}
2.2.3 白色人種
//白色人種
public class WhiteHuman implements Human {
public void getColor(){
System.out.printin("自色人種的皮膚顏色是白色的! ");
}
public void talk() {
System.out.printIn("白色人種會說話笛匙,一般都是但是單字節(jié)妹孙。");
}
}
2.3 八卦爐(工廠)
所有的人種定義完畢秋柄,下一步就是定義一個八卦爐,然后燒制人類蠢正。我們想象一下骇笔,女媧最可能給八卦爐下達(dá)什么樣的生產(chǎn)命令呢?應(yīng)該是”給我生產(chǎn)出一個黃色人種( YellowHuman類 )”嚣崭,而不會是“給我生產(chǎn)一個會走笨触、會跑、會說話雹舀、皮膚是黃色的人種”芦劣,因為這樣的命令增加了交流的成本,作為一個生產(chǎn)的管理者葱跋,只要知道生產(chǎn)什么就可以了持寄,而不需要事物的具體信息。通過分析娱俺,我們發(fā)現(xiàn)八卦爐生產(chǎn)人類的方法輸人參數(shù)類型應(yīng)該是Human接口的實現(xiàn)類稍味,這也解釋了為什么類圖上的 AbstractHumanFactory 抽象類中 createHuman 方法的參數(shù)為 Class類型。其源代碼如下所示荠卷。
//抽象人類創(chuàng)建工廠
public abstract class AbstractHumanFactory {
public abstract <T extends Human> T createHuman (Class<T> c);
}
注意模庐,我們在這里采用了泛型(Generic), 通過定義泛型對tcreateHuman的輸人參數(shù)產(chǎn)生兩層限制:
必須是Class類型;
-
必須是Human的實現(xiàn)類。
其中的 “?” 表示的是油宜,只要實現(xiàn)了Human接口的類都可以作為參數(shù)掂碱,泛型是JDK 1.5中的一個非常重要的新特性怜姿,它減少了對象間的轉(zhuǎn)換,約束其輸入?yún)?shù)類型疼燥,對Collection集合下的實現(xiàn)類都可以定義泛型沧卢。有關(guān)泛型的詳細(xì)知識,請參考相關(guān)的Java語法文檔醉者。目前女媧只有一個八卦爐但狭,其實現(xiàn)生產(chǎn)人類的方法,如下所示撬即。
public class HumanFactory extends AbstractHumanFactory{
public <T extends Human> T createteHuman(Class<T> c){
//定義一個生產(chǎn)的人種
Human human = null;
try {
//產(chǎn)生一個人種
human = (Human)Class.forName(c.gettName()).newInstance);
} catch(Exception e){
System.out.Println("人種生成錯誤!");
}
return (T)human;
}
}
2.3.1 造人過程(客戶)
人種有了立磁,八卦爐也有了,剩下的工作就是女媧采集黃土剥槐,然后命令八卦爐開始生產(chǎn)唱歧,其過程如下所示。
public class NvWa {
public static void main(string[] args) {
//聲明陰陽八卦爐
AbstractHumanFactory YinYangLu = new Humanractory();
//女媧第一次道人粒竖,火候不足颅崩,于是白人產(chǎn)生了
System.out.println("--造出的第一批人是白色人種--");
Human whiteHuman = yinYangLu.createHuman(WhiteHuman.class);
whiteHuman.getColor();
whiteHuman.ta1k();
//女媧第二次道人,火候過足温圆,于是黑人產(chǎn)生了
System.out.println("--造出的第二批人是黑色人種--");
Human blackHuman = yinYangLu.createHuman(BlackHuman.class);
blackHuman.getColor();
blackHuman.ta1k();
//女媧第三次道人挨摸,火候剛好,于是黃色人種產(chǎn)生了
System.out.println("--造出的第三批人是黃色人種--");
Human yelloHuman = yinYangLu.createHuman(YelloHuman.class);
yelloHuman.getColor();
yelloHuman.ta1k();
}
}
但岁歉,這就結(jié)束了嗎?請接著往下看膝蜈。
2.4 八卦爐改造(簡單工廠模式)
我們這樣考慮一一個問題:一個模塊僅需要一個工廠類锅移,沒有必要把它產(chǎn)生出來,使用靜態(tài)的方法就可以了饱搏,根據(jù)這一要求非剃,我們把上例中的 AbstarctHumanFactory 修改一下,代碼如下
public class HumanFactory{
//將工廠創(chuàng)建對象的方法設(shè)置為靜態(tài)
public static <T extends Human> T createteHuman(Class<T> c){
//定義一個生產(chǎn)的人種
Human human = null;
try {
//產(chǎn)生一個人種
human = (Human)Class.forName(c.gettName()).newInstance);
} catch(Exception e){
System.out.Println("人種生成錯誤!");
}
return (T)human;
}
}
這樣推沸,我們的女媧在使用八卦爐時就不用去創(chuàng)建八卦爐對象了备绽,可以直接使用Humanractory去創(chuàng)建人類了。
public class NvWa {
public static void main(string[] args) {
//女媧第一次道人鬓催,火候不足肺素,于是白人產(chǎn)生了
System.out.println("--造出的第一批人是白色人種--");
Human whiteHuman = Humanractory.createHuman(WhiteHuman.class);
whiteHuman.getColor();
whiteHuman.ta1k();
//女媧第二次道人,火候過足宇驾,于是黑人產(chǎn)生了
System.out.println("--造出的第二批人是黑色人種--");
Human blackHuman = Humanractory.createHuman(BlackHuman.class);
blackHuman.getColor();
blackHuman.ta1k();
//女媧第三次道人倍靡,火候剛好,于是黃色人種產(chǎn)生了
System.out.println("--造出的第三批人是黃色人種--");
Human yelloHuman = Humanractory.createHuman(YelloHuman.class);
yelloHuman.getColor();
yelloHuman.ta1k();
}
}
2.5 多幾個八卦爐(多工廠模式)
當(dāng)我們在做一個比較復(fù)雜的項目時课舍,經(jīng)常會遇到初始化一個對象很耗費精力的情況塌西,所有的產(chǎn)品類都放到一個工廠方法中進行初始化會使代碼結(jié)構(gòu)不清晰他挎。例如,一個產(chǎn)品類有5個具體實現(xiàn)捡需,每個實現(xiàn)類的初始化(不僅僅是new办桨,初始化包括new一個對象,并對對象設(shè)置一定的初始值)方法都不相同站辉,如果寫在一個工廠方法中崔挖,勢必會導(dǎo)致該方法巨大無比,那該怎么辦?
考慮到需要結(jié)構(gòu)清晰庵寞,我們就為每個產(chǎn)品定義一個創(chuàng)造者狸相,然后由調(diào)用者自己去選擇與哪個工廠方法關(guān)聯(lián)。我們還是以女媧造人為例捐川,每個人種都有一個固定的八卦爐脓鹃,分別造出黑色人種、白色人種古沥、黃色人種瘸右。
2.5.1 多工廠模式的抽象工廠類代碼
//多工廠模式的抽象工廠類
public abstract class AbstractHumanFactory{
public abstract Human creteHuman();
}
2.5.2 各色人種的創(chuàng)建工廠如代碼所示。
//黑色人種的創(chuàng)建工廠實現(xiàn)
public class BlackHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new BlackHuman();
}
}
//黃色人種的創(chuàng)建工廠實現(xiàn)
public class Ye1lowHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new Ye1loWHuman();
}
}
//白色人種的創(chuàng)建工廠實現(xiàn)
public class YellowHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new WhiteHuman();
}
2.5.3 女媧多了好多爐子
public class NvWa {
public static void main(String[] args) {
//女媧第一次造人岩齿,火候不足太颤,于是白色人種產(chǎn)生了
System.out.print1n("--造出的第一批人是白色人種--");
Human whiteHuman = (new whlteHumanractory()).createHuman();
whiteHuman.getColor();
whiteHuman.talk();
//女媧第二次造人,火候過足盹沈,于是黑色人種產(chǎn)生了
System.out.println("--造出的第二批人是黑色人種--") ;
Human b1ackHuman = (new BlackHumanFactory()).createHuman();
blackHuman.getColor();
blackHuman.talk();
//第三次造人龄章,火候剛剛好,于是黃色人種產(chǎn)生了
System.out.println("--造出的第三批人是黃色人種--");
Human yellowHuman = (new YellowHumanFactory()).createRuman();
yellowHuman.getColor();
yellowHuman.talk();
}
}
運行結(jié)果還是相同乞封。我們回顧一下做裙, 每一個產(chǎn)品類都對應(yīng)了一個創(chuàng)建類,好處就是創(chuàng)建類的職責(zé)清晰肃晚,而且結(jié)構(gòu)簡單锚贱,但是給可擴展性和可維護性帶來了一定的影響。為什么這么說呢关串?
如果要擴展一個產(chǎn)品類拧廊,就需要建立一個相應(yīng)的工廠類,這樣就增加了擴展的難度晋修。因為工廠類和產(chǎn)品類的數(shù)量相同吧碾,維護時需要考慮兩個對象之間的關(guān)系。
當(dāng)然飞蚓,在復(fù)雜的應(yīng)用中一般采用多工廠的方法滤港,然后再增加一個協(xié)調(diào)類,避免調(diào)用者與各個子工廠交流,協(xié)調(diào)類的作用是封裝子工廠類,對高層模塊提供統(tǒng)一的訪問接口溅漾。
3山叮、拓展工廠
3.1 替代單例模式
拋開上面的背景故事,回到單例模式添履,我們是不是可以采用工廠方法模式實現(xiàn)單例模式的功能呢屁倔?單例模式的核心要求就是在內(nèi)存中只有一個對象,通過工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個對象暮胧,代碼如下所示锐借。
//單例類
public class Singleton {
//不允許通過new產(chǎn)生一個對象
private Singleton() {}
public void doSomething() {
//業(yè)務(wù)處理
}
}
Singleton保證不能通過正常的渠道建立一個對象,那 SingletonFactory 如何建立一個單例對象呢往衷?答案是通過反射方式創(chuàng)建钞翔,如代碼清單下所示。
//負(fù)責(zé)生成單例的工廠類
pub1ic class SingletonFactory {
private static Singleton singleton;
static{
try {
Class cl = Class.forName(Singleton.class.getName());
//獲得無參構(gòu)造
Constructor constructor = cl.getDeclaredConstructor();
//設(shè)置無參構(gòu)造是可訪問的
constructor.setAccessible(true);
//產(chǎn)生一個實例對象
singleton = (Sing1eton) constructor.newInstance();
} catch (Exception e) {
//異常處理
}
}
public static Singleton getSingleton() {
return singleton;
}
}
通過獲得類構(gòu)造器席舍,然后設(shè)置訪問權(quán)限布轿,生成一個對象,然后提供外部訪問来颤,保證內(nèi)存中的對象唯一汰扭。當(dāng)然,其他類也可以通過反射的方式建立一個單例對象福铅,確實如此萝毛,但是一個項目或團隊是有章程和規(guī)范的,何況已經(jīng)提供了一個獲得單例對象的方法滑黔,為什么還要重新創(chuàng)建一個新對象呢笆包?除非是有人作惡。
以上通過工廠方法模式創(chuàng)建了一個單例對象拷沸,該框架可以繼續(xù)擴展色查,在一個項目中可以產(chǎn)生一個單例構(gòu)造器,所有需要產(chǎn)生單例的類都遵循一定的規(guī)則(構(gòu)造方法是private)撞芍,然后通過擴展該框架,只要輸入一個類型就可以獲得唯一的一個實例跨扮。
3.2 延遲加載的工廠類
//延遲加載的工廠類
public class ProductFactory{
//創(chuàng)建一個map對象用來存放各種對象
private static final Map<String, Product> prMap = new HashMap();
//創(chuàng)建對象
public static synchronized Product createProduct(String type) throws Exception{
//首先定義對象為空
Product product =nu11 ;
//如果Map中已經(jīng)有這個對象
if(prMap.containsKey(type)) {
//從Map中獲取在這個對象并返回
product = prMap.get(type);
}else{
//如果Map中不存在這個對象序无,則匹配輸入類型,判斷創(chuàng)建什么對象
if(type.equa1s("Product1")) {
product = new ConcreteProduct1();
}else{
product = new ConcreteProduct2();
//同時把對象放到緩存容器中
prMap.put(type,product);
}
//返回所需要的對象
return product;
}
}
代碼還比較簡單衡创,通過定義一個Map容器帝嗡,容納所有產(chǎn)生的對象,如果在Map容器中已經(jīng)有的對象璃氢,則直接取出返回哟玷;如果沒有,則根據(jù)需要的類型產(chǎn)生一個對象并放入到Map容器中,以防便下次調(diào)用。延遲加載框架是可以擴展的巢寡,例如限制某一個產(chǎn) 品類的最大實例化數(shù)量喉脖,可以通過判斷Map中已有的對象數(shù)量來實現(xiàn),這樣的處理是非常有意義的抑月,例如 JDBC 連接數(shù)據(jù)庫树叽,都會要求設(shè)置一個 MaxConnections 最大連接數(shù)量,該數(shù)量就是內(nèi)存中最大實例化的數(shù)量谦絮。延遲加載還可以用在對象初始化比較復(fù)雜的情況下题诵,例如硬件訪問,涉及多方面的交互层皱,則可以通過延遲加載降低對象的產(chǎn)生和銷毀帶來的復(fù)雜性性锭。