設(shè)計模式
設(shè)計原則:
要依賴抽象逗载,不要依賴具體類
簡單工廠模式雖然簡單哆窿,但存在一個很嚴重的問題。當(dāng)系統(tǒng)中需要引入新產(chǎn)品時厉斟,由于靜態(tài)工廠方法通過所傳入?yún)?shù)的不同來創(chuàng)建不同的產(chǎn)品挚躯,這必定要修改工廠類的源代碼,將違背“開閉原則”擦秽,如何實現(xiàn)增加新產(chǎn)品而不影響已有代碼码荔?工廠方法模式應(yīng)運而生,本文將介紹第二種工廠模式——工廠方法模式感挥。
1 什么是工廠方法模式
工廠方法模式(Factory Method Pattern)又稱為工廠模式缩搅,也叫虛擬構(gòu)造器(Virtual Constructor)模式或者多態(tài)工廠(Polymorphic Factory)模式,它屬于類創(chuàng)建型模式触幼。
在工廠方法模式中硼瓣,工廠父類負責(zé)定義創(chuàng)建產(chǎn)品對象的公共接口,而工廠子類則負責(zé)生成具體的產(chǎn)品對象置谦,這樣做的目的是將產(chǎn)品類的實例化操作延遲到工廠子類中完成堂鲤,即通過工廠子類來確定究竟應(yīng)該實例化哪一個具體產(chǎn)品類亿傅。
2 為什么要用該模式
在簡單工廠模式中只提供一個工廠類,該工廠類處于對產(chǎn)品類進行實例化的中心位置瘟栖,它需要知道每一個產(chǎn)品對象的創(chuàng)建細節(jié)葵擎,并決定何時實例化哪一個產(chǎn)品類。簡單工廠模式最大的缺點是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時慢宗,必須修改工廠類坪蚁,需要在其中加入必要的業(yè)務(wù)邏輯,這違背了“開閉原則”镜沽。
此外敏晤,在簡單工廠模式中,所有的產(chǎn)品都由同一個工廠創(chuàng)建缅茉,工廠類職責(zé)較重嘴脾,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類之間的耦合度高蔬墩,嚴重影響了系統(tǒng)的靈活性和擴展性译打,而工廠方法模式則可以很好地解決這一問題。
在工廠方法模式中拇颅,不再提供一個統(tǒng)一的工廠類來創(chuàng)建所有的產(chǎn)品對象奏司,而是針對不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個與產(chǎn)品等級結(jié)構(gòu)對應(yīng)的工廠等級結(jié)構(gòu)樟插。
3 模式的結(jié)構(gòu)
在工廠方法模式結(jié)構(gòu)圖中包含如下幾個角色:
Factory(抽象工廠類):在抽象工廠類中韵洋,聲明了工廠方法(Factory Method),用于返回一個產(chǎn)品黄锤。抽象工廠是工廠方法模式的核心搪缨,所有創(chuàng)建對象的工廠類都必須實現(xiàn)該接口。
ConcreteFactory(具體工廠類):它是抽象工廠類的子類鸵熟,實現(xiàn)了抽象工廠中定義的工廠方法副编,并可由客戶端調(diào)用,返回一個具體產(chǎn)品類的實例流强。
Product(抽象產(chǎn)品類):它是定義產(chǎn)品的接口痹届,是工廠方法模式所創(chuàng)建對象的超類型,也就是產(chǎn)品對象的公共父類打月。
(ConcreteProduct具體產(chǎn)品類):它實現(xiàn)了抽象產(chǎn)品接口短纵,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對應(yīng)僵控。
與簡單工廠模式相比,工廠方法模式最重要的區(qū)別是引入了抽象工廠角色鱼冀,抽象工廠可以是接口报破,也可以是抽象類或者具體類悠就。
4 代碼示例
4.1 抽象產(chǎn)品
/**
* Created by w1992wishes on 2017/11/01.
*/
public abstract class Cake {
void prepare(){
System.out.println("step 1......");
System.out.println("step 2......");
System.out.println("step 3......");
System.out.println("step 4......");
}
void bake(){
System.out.println("bake");
}
void box(){
System.out.println("box");
}
}
4.2 具體產(chǎn)品
* Created by w1992wishes on 2017/10/31.
*/
public class CenterCheeseCake extends Cake {
public CenterCheeseCake(){
name = "center cheese cake";
}
@Override
public void bake(){
System.out.println("不用烘箱,我要用火烤充易!");
}
}
/**
* Created by w1992wishes on 2017/11/1.
*/
public class CollegeFruitCake extends Cake {
public CollegeFruitCake(){
name = "center fruit cake";
}
@Override
public void box(){
System.out.println("不用圓盒子打包梗脾,我愛國,用五角星盒子盹靴!");
}
}
4.3 抽象工廠
首先定義一個抽象工廠炸茧,這個抽象工廠有一個抽象方法用于生產(chǎn)具體產(chǎn)品:
/**
* Created by w1992wishes on 2017/11/01.
*/
public abstract class CakeStore {
public Cake orderCake(String type) {
Cake cake;
cake = createCake(type);
cake.bake();
cake.box();
return cake;
}
protected abstract Cake createCake(String type);
}
4.4 具體工廠類
在抽象工廠中聲明了工廠方法但并未實現(xiàn)工廠方法,具體產(chǎn)品對象的創(chuàng)建由其子類負責(zé)稿静,客戶端針對抽象工廠編程梭冠,可在運行時再指定具體工廠類,具體工廠類實現(xiàn)了工廠方法改备,不同的具體工廠可以創(chuàng)建不同的具體產(chǎn)品控漠。
/**
* Created by w1992wishes on 2017/11/1.
*/
public class CenterCakeStore extends CakeStore {
@Override
protected Cake createCake(String type) {
Cake cake = null;
if ("cheese".equals(type)) {
cake = new CenterCheeseCake();
} else if ("fruit".equals(type)) {
cake = new CenterFruitCake();
} else if ("cream".equals(type)) {
cake = new CenterCreamCake();
}
return cake;
}
}
/**
* Created by w1992wishes on 2017/11/1.
*/
public class CollegeCakeStore extends CakeStore {
@Override
protected Cake createCake(String type) {
Cake cake = null;
if ("cheese".equals(type)) {
cake = new CollegeCheeseCake();
} else if ("fruit".equals(type)) {
cake = new CollegeFruitCake();
} else if ("cream".equals(type)) {
cake = new CollegeCreamCake();
}
return cake;
}
}
5 優(yōu)點和缺點
5.1 優(yōu)點
在工廠方法模式中,工廠方法用來創(chuàng)建客戶端所需要的產(chǎn)品悬钳,同時還向客戶端隱藏了哪種具體產(chǎn)品類將被實例化這一細節(jié)盐捷,客戶端只需要關(guān)心所需產(chǎn)品對應(yīng)的工廠,無須關(guān)心創(chuàng)建細節(jié)默勾,甚至無須知道具體產(chǎn)品類的類名碉渡。
基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計是工廠方法模式的關(guān)鍵。它能夠使工廠可以自主確定創(chuàng)建何種產(chǎn)品對象母剥,而如何創(chuàng)建這個對象的細節(jié)則完全封裝在具體工廠內(nèi)部滞诺。工廠方法模式之所以又被稱為多態(tài)工廠模式,是因為所有的具體工廠類都具有同一抽象父類媳搪。
使用工廠方法模式的另一個優(yōu)點是在系統(tǒng)中加入新產(chǎn)品時铭段,無須修改抽象工廠和抽象產(chǎn)品提供的接口,無須修改客戶端秦爆,也無須修改其他的具體工廠和具體產(chǎn)品序愚,而只要添加一個具體工廠和具體產(chǎn)品就可以了。這樣等限,系統(tǒng)的可擴展性也就變得非常好爸吮,完全符合“開閉原則”。
5.2 缺點
- 在添加新產(chǎn)品時望门,需要編寫新的具體產(chǎn)品類形娇,而且還要提供與之對應(yīng)的具體工廠類,系統(tǒng)中類的個數(shù)將成對增加筹误,在一定程度上增加了系統(tǒng)的復(fù)雜度桐早,有更多的類需要編譯和運行,會給系統(tǒng)帶來一些額外的開銷。
- 由于考慮到系統(tǒng)的可擴展性哄酝,需要引入抽象層友存,在客戶端代碼中均使用抽象層進行定義,增加了系統(tǒng)的抽象性和理解難度陶衅,且在實現(xiàn)時可能需要用到DOM屡立、反射等技術(shù),增加了系統(tǒng)的實現(xiàn)難度搀军。
工廠方法模式比起簡單工廠模式更加符合開閉原則膨俐。
工廠模式需要額外創(chuàng)建諸多 Factory 類,也會增加代碼的復(fù)雜性罩句,而且焚刺,每個 Factory 類只是做簡單的 new 操作,功能非常單钡闹埂(只有一行代碼)檩坚,也沒必要設(shè)計成獨立的類,所以诅福,在這個應(yīng)用場景下匾委,簡單工廠模式簡單好用,比工廠方法模式更加合適氓润。
6 適用環(huán)境
- 一個類不知道它所需要的對象的類:在工廠方法模式中赂乐,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對應(yīng)的工廠即可咖气,具體的產(chǎn)品對象由具體工廠類創(chuàng)建挨措;客戶端需要知道創(chuàng)建具體產(chǎn)品的工廠類。
- 一個類通過其子類來指定創(chuàng)建哪個對象:在工廠方法模式中崩溪,對于抽象工廠類只需要提供一個創(chuàng)建產(chǎn)品的接口浅役,而由其子類來確定具體要創(chuàng)建的對象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則伶唯,在程序運行時觉既,子類對象將覆蓋父類對象,從而使得系統(tǒng)更容易擴展乳幸。
- 將創(chuàng)建對象的任務(wù)委托給多個工廠子類中的某一個瞪讼,客戶端在使用時可以無須關(guān)心是哪一個工廠子類創(chuàng)建產(chǎn)品子類,需要時再動態(tài)指定粹断,可將具體工廠類的類名存儲在配置文件或數(shù)據(jù)庫中符欠。
7 模式擴展
- 使用多個工廠方法:在抽象工廠角色中可以定義多個工廠方法,從而使具體工廠角色實現(xiàn)這些不同的工廠方法瓶埋,這些方法可以包含不同的業(yè)務(wù)邏輯希柿,以滿足對不同的產(chǎn)品對象的需求诊沪。
- 產(chǎn)品對象的重復(fù)使用:工廠對象將已經(jīng)創(chuàng)建過的產(chǎn)品保存到一個集合(如數(shù)組、List等)中狡汉,然后根據(jù)客戶對產(chǎn)品的請求娄徊,對集合進行查詢。如果有滿足要求的產(chǎn)品對象盾戴,就直接將該產(chǎn)品返回客戶端;如果集合中沒有這樣的產(chǎn)品對象兵多,那么就創(chuàng)建一個新的滿足要求的產(chǎn)品對象尖啡,然后將這個對象在增加到集合中,再返回給客戶端剩膘。
- 多態(tài)性的喪失和模式的退化:如果工廠僅僅返回一個具體產(chǎn)品對象衅斩,便違背了工廠方法的用意,發(fā)生退化怠褐,此時就不再是工廠方法模式了畏梆。一般來說,工廠對象應(yīng)當(dāng)有一個抽象的父類型奈懒,如果工廠等級結(jié)構(gòu)中只有一個具體工廠類的話奠涌,抽象工廠就可以省略,也將發(fā)生了退化磷杏。當(dāng)只有一個具體工廠溜畅,在具體工廠中可以創(chuàng)建所有的產(chǎn)品對象,并且工廠方法設(shè)計為靜態(tài)方法時极祸,工廠方法模式就退化成簡單工廠模式慈格。
之所以將某個代碼塊剝離出來,獨立為函數(shù)或者類遥金,原因是這個代碼塊的邏輯過于復(fù)雜浴捆,剝離之后能讓代碼更加清晰,更加可讀稿械、可維護选泻。
基于這個設(shè)計思想,當(dāng)對象的創(chuàng)建邏輯比較復(fù)雜溜哮,不只是簡單的 new 一下就可以滔金,而是要組合其他類對象,做各種初始化操作的時候茂嗓,我們推薦使用工廠方法模式餐茵,將復(fù)雜的創(chuàng)建邏輯拆分到多個工廠類中,讓每個工廠類都不至于過于復(fù)雜述吸。
而使用簡單工廠模式忿族,將所有的創(chuàng)建邏輯都放到一個工廠類中锣笨,會導(dǎo)致這個工廠類變得很復(fù)雜。