設(shè)計(jì)原則:
要依賴抽象,不要依賴具體類
目錄
本文的結(jié)構(gòu)如下:
- 什么是工廠方法模式
- 為什么要用該模式
- 模式的結(jié)構(gòu)
- 代碼示例
- 優(yōu)點(diǎn)和缺點(diǎn)
- 適用環(huán)境
- 模式應(yīng)用
- 模式擴(kuò)展
- 總結(jié)
一、前言
簡(jiǎn)單工廠模式雖然簡(jiǎn)單,但存在一個(gè)很嚴(yán)重的問(wèn)題。當(dāng)系統(tǒng)中需要引入新產(chǎn)品時(shí),由于靜態(tài)工廠方法通過(guò)所傳入?yún)?shù)的不同來(lái)創(chuàng)建不同的產(chǎn)品,這必定要修改工廠類的源代碼,將違背“開(kāi)閉原則”,如何實(shí)現(xiàn)增加新產(chǎn)品而不影響已有代碼?工廠方法模式應(yīng)運(yùn)而生,本文將介紹第二種工廠模式——工廠方法模式。
二、什么是工廠方法模式
工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫虛擬構(gòu)造器(Virtual Constructor)模式或者多態(tài)工廠(Polymorphic Factory)模式,它屬于類創(chuàng)建型模式。在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象,這樣做的目的是將產(chǎn)品類的實(shí)例化操作延遲到工廠子類中完成,即通過(guò)工廠子類來(lái)確定究竟應(yīng)該實(shí)例化哪一個(gè)具體產(chǎn)品類。
三、為什么要用該模式
3.1、官方解釋
在簡(jiǎn)單工廠模式中只提供一個(gè)工廠類,該工廠類處于對(duì)產(chǎn)品類進(jìn)行實(shí)例化的中心位置,它需要知道每一個(gè)產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),并決定何時(shí)實(shí)例化哪一個(gè)產(chǎn)品類。簡(jiǎn)單工廠模式最大的缺點(diǎn)是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時(shí),必須修改工廠類,需要在其中加入必要的業(yè)務(wù)邏輯,這違背了“開(kāi)閉原則”。
此外,在簡(jiǎn)單工廠模式中,所有的產(chǎn)品都由同一個(gè)工廠創(chuàng)建,工廠類職責(zé)較重,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類之間的耦合度高,嚴(yán)重影響了系統(tǒng)的靈活性和擴(kuò)展性,而工廠方法模式則可以很好地解決這一問(wèn)題。
在工廠方法模式中,不再提供一個(gè)統(tǒng)一的工廠類來(lái)創(chuàng)建所有的產(chǎn)品對(duì)象,而是針對(duì)不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個(gè)與產(chǎn)品等級(jí)結(jié)構(gòu)對(duì)應(yīng)的工廠等級(jí)結(jié)構(gòu)。
3.2、舉個(gè)例子
還是用蛋糕店的例子說(shuō)明一下。
你的蛋糕店很火熱,每天前來(lái)買蛋糕的人絡(luò)繹不絕,碰到休息日,更是火爆到不行,排隊(duì)的人都排到了“金拱門”的門口,忙碌的你富有沖勁,決定掏開(kāi)腰包,在另一個(gè)火爆地段--一所大學(xué)的門口再開(kāi)一家分店,并且因?yàn)榇髮W(xué)旁邊學(xué)生較多,你打算做一些改良,讓新開(kāi)的分店蛋糕的口味更適合年輕人。忙碌富裕的你決定再找個(gè)程序猿來(lái)幫忙設(shè)計(jì)代碼,但你就是葛朗臺(tái),你開(kāi)的價(jià)錢太低,你只給10RMB,沒(méi)有人肯干這個(gè)活,心好的我再次被你請(qǐng)來(lái)。
你給我說(shuō)了你的想法,我心里一陣竊喜,這可以用上次get的簡(jiǎn)單工廠模式啊,于是我是這樣設(shè)計(jì)的:
/**
* Created by w1992wishes on 2017/10/31.
*/
public class SimpleCakeFacroty {
public static Cake createCake(String location, String type){
Cake cake;
if ("center".equalsIgnoreCase(location)){
if ("cheese".equals(type)) {
cake = new CenterCheeseCake();
} else if ("fruit".equals(type)) {
cake = new CenterFruitCake();
} else if ("cream".equals(type)) {
cake = new CenterCreamCake();
} else {
cake = new CenterDefaultCake();
}
}else if("college".equalsIgnoreCase(location)){
if ("cheese".equals(type)) {
cake = new CollegeCheeseCake();
} else if ("fruit".equals(type)) {
cake = new CollegeFruitCake();
} else if ("cream".equals(type)) {
cake = new CollegeCreamCake();
} else {
cake = new CollegeDefaultCake();
}
}else if("other".equalsIgnoreCase(location)){
if ("cheese".equals(type)) {
cake = new OtherCheeseCake();
} else if ("fruit".equals(type)) {
cake = new OtherFruitCake();
} else if ("cream".equals(type)) {
cake = new OtherCreamCake();
} else {
cake = new OtherDefaultCake();
}
}
return cake;
}
}
所以CakeStore是這樣的:
public class CakeStore {
public Cake orderCake(String location, String type) {
Cake cake;
cake = SimpleCakeFacroty.createCake(location, type);
cake.bake();
cake.box();
return cake;
}
}
一個(gè)上午的功夫,我認(rèn)真寫出這段代碼,帶著滿滿的成就感把它交給了你,你拍了拍手上的面粉,結(jié)果后只瞥了一眼,就噴著口水對(duì)我說(shuō):“你寫的代碼就是一堆狗屎?!?/p>
沒(méi)有任何猶豫,你再次辭退了我,但你的職業(yè)精神我很敬佩,在我垂頭喪氣離開(kāi)之前,你沒(méi)有給我10RMB的報(bào)酬,而是咬著牙對(duì)我說(shuō):
雖然你用了簡(jiǎn)單工廠模式,實(shí)現(xiàn)了蛋糕的創(chuàng)建和消費(fèi)分離,但是這里卻存在嚴(yán)重問(wèn)題:
- 大量的if...else...相互嵌套,邏輯復(fù)雜,代碼不直觀,導(dǎo)致維護(hù)和測(cè)試都很困難,這真是最糟糕的代碼;
- 擴(kuò)展不靈活,必須修改靜態(tài)工廠方法的業(yè)務(wù)邏輯,違反了“開(kāi)閉原則”。
- 工廠方法和具體的蛋糕類嚴(yán)重耦合,而且具體的蛋糕類特別多,嚴(yán)重違背了“要依賴抽象,不要依賴具體”的設(shè)計(jì)原則。
- ......
怎么解決這個(gè)問(wèn)題呢?工廠方法模式正好合適。具體代碼呢?先等介紹完工廠方法模式的結(jié)構(gòu)再看啦。
四、模式的結(jié)構(gòu)

在工廠方法模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
- Factory(抽象工廠類):在抽象工廠類中,聲明了工廠方法(Factory Method),用于返回一個(gè)產(chǎn)品。抽象工廠是工廠方法模式的核心,所有創(chuàng)建對(duì)象的工廠類都必須實(shí)現(xiàn)該接口。
- ConcreteFactory(具體工廠類):它是抽象工廠類的子類,實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶端調(diào)用,返回一個(gè)具體產(chǎn)品類的實(shí)例。
- Product(抽象產(chǎn)品類):它是定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建對(duì)象的超類型,也就是產(chǎn)品對(duì)象的公共父類。
- (ConcreteProduct具體產(chǎn)品類):它實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對(duì)應(yīng)。
與簡(jiǎn)單工廠模式相比,工廠方法模式最重要的區(qū)別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類。
五、代碼示例
首先定義一個(gè)抽象工廠,這個(gè)抽象工廠有一個(gè)抽象方法用于生產(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);
}
在抽象工廠中聲明了工廠方法但并未實(shí)現(xiàn)工廠方法,具體產(chǎn)品對(duì)象的創(chuàng)建由其子類負(fù)責(zé),客戶端針對(duì)抽象工廠編程,可在運(yùn)行時(shí)再指定具體工廠類,具體工廠類實(shí)現(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;
}
}
抽象產(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");
}
}
具體產(chǎn)品有自己獨(dú)有的bake(),box()方法:
* 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("不用圓盒子打包,我愛(ài)國(guó),用五角星盒子!");
}
}
最后客戶端:
public class Client {
public static void main(String[] args) {
//這里可通過(guò)引入配置文件更改
CakeStore cakeStore = new CenterCakeStore();
Cake cake = cakeStore.orderCake("cheese");
}
}
這樣一改,往后想再新開(kāi)一個(gè)蛋糕店,只需繼承自CakeStore新增生產(chǎn)具體的Cake,而不需改動(dòng)源代碼,這樣就符合了“開(kāi)閉原則”;而且客戶端更換蛋糕店可以通過(guò)配置來(lái)完成,同樣不需修改源代碼。
六、優(yōu)點(diǎn)和缺點(diǎn)
6.1、優(yōu)點(diǎn)
- 在工廠方法模式中,工廠方法用來(lái)創(chuàng)建客戶端所需要的產(chǎn)品,同時(shí)還向客戶端隱藏了哪種具體產(chǎn)品類將被實(shí)例化這一細(xì)節(jié),客戶端只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無(wú)須關(guān)心創(chuàng)建細(xì)節(jié),甚至無(wú)須知道具體產(chǎn)品類的類名。
- 基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計(jì)是工廠方法模式的關(guān)鍵。它能夠使工廠可以自主確定創(chuàng)建何種產(chǎn)品對(duì)象,而如何創(chuàng)建這個(gè)對(duì)象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱為多態(tài)工廠模式,是因?yàn)樗械木唧w工廠類都具有同一抽象父類。
- 使用工廠方法模式的另一個(gè)優(yōu)點(diǎn)是在系統(tǒng)中加入新產(chǎn)品時(shí),無(wú)須修改抽象工廠和抽象產(chǎn)品提供的接口,無(wú)須修改客戶端,也無(wú)須修改其他的具體工廠和具體產(chǎn)品,而只要添加一個(gè)具體工廠和具體產(chǎn)品就可以了。這樣,系統(tǒng)的可擴(kuò)展性也就變得非常好,完全符合“開(kāi)閉原則”。
6.2、缺點(diǎn)
- 在添加新產(chǎn)品時(shí),需要編寫新的具體產(chǎn)品類,而且還要提供與之對(duì)應(yīng)的具體工廠類,系統(tǒng)中類的個(gè)數(shù)將成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,有更多的類需要編譯和運(yùn)行,會(huì)給系統(tǒng)帶來(lái)一些額外的開(kāi)銷。
- 由于考慮到系統(tǒng)的可擴(kuò)展性,需要引入抽象層,在客戶端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度,且在實(shí)現(xiàn)時(shí)可能需要用到DOM、反射等技術(shù),增加了系統(tǒng)的實(shí)現(xiàn)難度。
七、適用環(huán)境
在以下情況下可以使用工廠方法模式:
- 一個(gè)類不知道它所需要的對(duì)象的類:在工廠方法模式中,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對(duì)應(yīng)的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠類創(chuàng)建;客戶端需要知道創(chuàng)建具體產(chǎn)品的工廠類。
- 一個(gè)類通過(guò)其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象:在工廠方法模式中,對(duì)于抽象工廠類只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類來(lái)確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則,在程序運(yùn)行時(shí),子類對(duì)象將覆蓋父類對(duì)象,從而使得系統(tǒng)更容易擴(kuò)展。
- 將創(chuàng)建對(duì)象的任務(wù)委托給多個(gè)工廠子類中的某一個(gè),客戶端在使用時(shí)可以無(wú)須關(guān)心是哪一個(gè)工廠子類創(chuàng)建產(chǎn)品子類,需要時(shí)再動(dòng)態(tài)指定,可將具體工廠類的類名存儲(chǔ)在配置文件或數(shù)據(jù)庫(kù)中。
八、模式應(yīng)用
JDBC中的工廠方法:
Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://loc
alhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");
九、模式擴(kuò)展
- 使用多個(gè)工廠方法:在抽象工廠角色中可以定義多個(gè)工廠方法,從而使具體工廠角色實(shí)現(xiàn)這些不同的工廠方法,這些方法可以包含不同的業(yè)務(wù)邏輯,以滿足對(duì)不同的產(chǎn)品對(duì)象的需求。
- 產(chǎn)品對(duì)象的重復(fù)使用:工廠對(duì)象將已經(jīng)創(chuàng)建過(guò)的產(chǎn)品保存到一個(gè)集合(如數(shù)組、List等)中,然后根據(jù)客戶對(duì)產(chǎn)品的請(qǐng)求,對(duì)集合進(jìn)行查詢。如果有滿足要求的產(chǎn)品對(duì)象,就直接將該產(chǎn)品返回客戶端;如果集合中沒(méi)有這樣的產(chǎn)品對(duì)象,那么就創(chuàng)建一個(gè)新的滿足要求的產(chǎn)品對(duì)象,然后將這個(gè)對(duì)象在增加到集合中,再返回給客戶端。
- 多態(tài)性的喪失和模式的退化:如果工廠僅僅返回一個(gè)具體產(chǎn)品對(duì)象,便違背了工廠方法的用意,發(fā)生退化,此時(shí)就不再是工廠方法模式了。一般來(lái)說(shuō),工廠對(duì)象應(yīng)當(dāng)有一個(gè)抽象的父類型,如果工廠等級(jí)結(jié)構(gòu)中只有一個(gè)具體工廠類的話,抽象工廠就可以省略,也將發(fā)生了退化。當(dāng)只有一個(gè)具體工廠,在具體工廠中可以創(chuàng)建所有的產(chǎn)品對(duì)象,并且工廠方法設(shè)計(jì)為靜態(tài)方法時(shí),工廠方法模式就退化成簡(jiǎn)單工廠模式。
十、總結(jié)
- 工廠方法模式又稱為工廠模式,它屬于類創(chuàng)建型模式。在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象,這樣做的目的是將產(chǎn)品類的實(shí)例化操作延遲到工廠子類中完成,即通過(guò)工廠子類來(lái)確定究竟應(yīng)該實(shí)例化哪一個(gè)具體產(chǎn)品類。
- 工廠方法模式包含四個(gè)角色:抽象產(chǎn)品是定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建對(duì)象的超類型,即產(chǎn)品對(duì)象的共同父類或接口;具體產(chǎn)品實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,它們之間往往一一對(duì)應(yīng);抽象工廠中聲明了工廠方法,用于返回一個(gè)產(chǎn)品,它是工廠方法模式的核心,任何在模式中創(chuàng)建對(duì)象的工廠類都必須實(shí)現(xiàn)該接口;具體工廠是抽象工廠類的子類,實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶調(diào)用,返回一個(gè)具體產(chǎn)品類的實(shí)例。
- 工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象和推廣。由于使用了面向?qū)ο蟮亩鄳B(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。在工廠方法模式中,核心的工廠類不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建工作交給子類去做。這個(gè)核心類僅僅負(fù)責(zé)給出具體工廠必須實(shí)現(xiàn)的接口,而不負(fù)責(zé)產(chǎn)品類被實(shí)例化這種細(xì)節(jié),這使得工廠方法模式可以允許系統(tǒng)在不修改工廠角色的情況下引進(jìn)新產(chǎn)品。
- 工廠方法模式的主要優(yōu)點(diǎn)是增加新的產(chǎn)品類時(shí)無(wú)須修改現(xiàn)有系統(tǒng),并封裝了產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),系統(tǒng)具有良好的靈活性和可擴(kuò)展性;其缺點(diǎn)在于增加新產(chǎn)品的同時(shí)需要增加新的工廠,導(dǎo)致系統(tǒng)類的個(gè)數(shù)成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜性。
- 工廠方法模式適用情況包括:一個(gè)類不知道它所需要的對(duì)象的類;一個(gè)類通過(guò)其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象;將創(chuàng)建對(duì)象的任務(wù)委托給多個(gè)工廠子類中的某一個(gè),客戶端在使用時(shí)可以無(wú)須關(guān)心是哪一個(gè)工廠子類創(chuàng)建產(chǎn)品子類,需要時(shí)再動(dòng)態(tài)指定。