設計原則:
要依賴抽象,不要依賴具體類
目錄
本文的結構如下:
- 什麼是工廠方法模式
- 爲什麼要用該模式
- 模式的結構
- 代碼示例
- 優點和缺點
- 適用環境
- 模式應用
- 模式擴展
- 總結
一、前言
簡單工廠模式雖然簡單,但存在一個很嚴重的問題。當系統中需要引入新產品時,由於靜態工廠方法通過所傳入參數的不同來創建不同的產品,這必定要修改工廠類的源代碼,將違背“開閉原則”,如何實現增加新產品而不影響已有代碼?工廠方法模式應運而生,本文將介紹第二種工廠模式——工廠方法模式。
二、什麼是工廠方法模式
工廠方法模式(Factory Method Pattern)又稱爲工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,它屬於類創建型模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
三、爲什麼要用該模式
3.1、官方解釋
在簡單工廠模式中只提供一個工廠類,該工廠類處於對產品類進行實例化的中心位置,它需要知道每一個產品對象的創建細節,並決定何時實例化哪一個產品類。簡單工廠模式最大的缺點是當有新產品要加入到系統中時,必須修改工廠類,需要在其中加入必要的業務邏輯,這違背了“開閉原則”。
此外,在簡單工廠模式中,所有的產品都由同一個工廠創建,工廠類職責較重,業務邏輯較爲複雜,具體產品與工廠類之間的耦合度高,嚴重影響了系統的靈活性和擴展性,而工廠方法模式則可以很好地解決這一問題。
在工廠方法模式中,不再提供一個統一的工廠類來創建所有的產品對象,而是針對不同的產品提供不同的工廠,系統提供一個與產品等級結構對應的工廠等級結構。
3.2、舉個例子
還是用蛋糕店的例子說明一下。
你的蛋糕店很火熱,每天前來買蛋糕的人絡繹不絕,碰到休息日,更是火爆到不行,排隊的人都排到了“金拱門”的門口,忙碌的你富有衝勁,決定掏開腰包,在另一個火爆地段–一所大學的門口再開一家分店,並且因爲大學旁邊學生較多,你打算做一些改良,讓新開的分店蛋糕的口味更適合年輕人。忙碌富裕的你決定再找個程序猿來幫忙設計代碼,但你就是葛朗臺,你開的價錢太低,你只給10RMB,沒有人肯幹這個活,心好的我再次被你請來。
你給我說了你的想法,我心裏一陣竊喜,這可以用上次get的簡單工廠模式啊,於是我是這樣設計的:
/**
* 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;
}
}
一個上午的功夫,我認真寫出這段代碼,帶着滿滿的成就感把它交給了你,你拍了拍手上的麪粉,結果後只瞥了一眼,就噴着口水對我說:“你寫的代碼就是一堆狗屎。”
沒有任何猶豫,你再次辭退了我,但你的職業精神我很敬佩,在我垂頭喪氣離開之前,你沒有給我10RMB的報酬,而是咬着牙對我說:
雖然你用了簡單工廠模式,實現了蛋糕的創建和消費分離,但是這裏卻存在嚴重問題:
- 大量的if…else…相互嵌套,邏輯複雜,代碼不直觀,導致維護和測試都很困難,這真是最糟糕的代碼;
- 擴展不靈活,必須修改靜態工廠方法的業務邏輯,違反了“開閉原則”。
- 工廠方法和具體的蛋糕類嚴重耦合,而且具體的蛋糕類特別多,嚴重違背了“要依賴抽象,不要依賴具體”的設計原則。
- ……
怎麼解決這個問題呢?工廠方法模式正好合適。具體代碼呢?先等介紹完工廠方法模式的結構再看啦。
四、模式的結構
在工廠方法模式結構圖中包含如下幾個角色:
- Factory(抽象工廠類):在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,所有創建對象的工廠類都必須實現該接口。
- ConcreteFactory(具體工廠類):它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。
- Product(抽象產品類):它是定義產品的接口,是工廠方法模式所創建對象的超類型,也就是產品對象的公共父類。
- ConcreteProduct(具體產品類):它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,具體工廠和具體產品之間一一對應。
與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類。
五、代碼示例
首先定義一個抽象工廠,這個抽象工廠有一個抽象方法用於生產具體產品:
/**
* 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);
}
在抽象工廠中聲明瞭工廠方法但並未實現工廠方法,具體產品對象的創建由其子類負責,客戶端針對抽象工廠編程,可在運行時再指定具體工廠類,具體工廠類實現了工廠方法,不同的具體工廠可以創建不同的具體產品。
/**
* 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;
}
}
抽象產品:
/**
* 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");
}
}
具體產品有自己獨有的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("不用圓盒子打包,我愛國,用五角星盒子!");
}
}
最後客戶端:
public class Client {
public static void main(String[] args) {
//這裏可通過引入配置文件更改
CakeStore cakeStore = new CenterCakeStore();
Cake cake = cakeStore.orderCake("cheese");
}
}
這樣一改,往後想再新開一個蛋糕店,只需繼承自CakeStore新增生產具體的Cake,而不需改動源代碼,這樣就符合了“開閉原則”;而且客戶端更換蛋糕店可以通過配置來完成,同樣不需修改源代碼。
六、優點和缺點
6.1、優點
- 在工廠方法模式中,工廠方法用來創建客戶端所需要的產品,同時還向客戶端隱藏了哪種具體產品類將被實例化這一細節,客戶端只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
- 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱爲多態工廠模式,是因爲所有的具體工廠類都具有同一抽象父類。
- 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”。
6.2、缺點
- 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
- 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。
七、適用環境
在以下情況下可以使用工廠方法模式:
- 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
- 一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
- 將創建對象的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。
八、模式應用
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");
九、模式擴展
- 使用多個工廠方法:在抽象工廠角色中可以定義多個工廠方法,從而使具體工廠角色實現這些不同的工廠方法,這些方法可以包含不同的業務邏輯,以滿足對不同的產品對象的需求。
- 產品對象的重複使用:工廠對象將已經創建過的產品保存到一個集合(如數組、List等)中,然後根據客戶對產品的請求,對集合進行查詢。如果有滿足要求的產品對象,就直接將該產品返回客戶端;如果集合中沒有這樣的產品對象,那麼就創建一個新的滿足要求的產品對象,然後將這個對象在增加到集合中,再返回給客戶端。
- 多態性的喪失和模式的退化:如果工廠僅僅返回一個具體產品對象,便違背了工廠方法的用意,發生退化,此時就不再是工廠方法模式了。一般來說,工廠對象應當有一個抽象的父類型,如果工廠等級結構中只有一個具體工廠類的話,抽象工廠就可以省略,也將發生了退化。當只有一個具體工廠,在具體工廠中可以創建所有的產品對象,並且工廠方法設計爲靜態方法時,工廠方法模式就退化成簡單工廠模式。
十、總結
- 工廠方法模式又稱爲工廠模式,它屬於類創建型模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
- 工廠方法模式包含四個角色:抽象產品是定義產品的接口,是工廠方法模式所創建對象的超類型,即產品對象的共同父類或接口;具體產品實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,它們之間往往一一對應;抽象工廠中聲明瞭工廠方法,用於返回一個產品,它是工廠方法模式的核心,任何在模式中創建對象的工廠類都必須實現該接口;具體工廠是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶調用,返回一個具體產品類的實例。
- 工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了面向對象的多態性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的創建,而是將具體創建工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的接口,而不負責產品類被實例化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
- 工廠方法模式的主要優點是增加新的產品類時無須修改現有系統,並封裝了產品對象的創建細節,系統具有良好的靈活性和可擴展性;其缺點在於增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,在一定程度上增加了系統的複雜性。
- 工廠方法模式適用情況包括:一個類不知道它所需要的對象的類;一個類通過其子類來指定創建哪個對象;將創建對象的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定。