設計模式學習筆記四------工廠方法模式
設計原則:
要依賴抽象,不要依賴具體類
目錄
本文的結構如下:
- 什麼是工廠方法模式
- 為什麼要用該模式
- 模式的結構
- 程式碼示例
- 優點和缺點
- 適用環境
- 模式應用
- 模式擴充套件
- 總結
一、前言
簡單工廠模式雖然簡單,但存在一個很嚴重的問題。當系統中需要引入新產品時,由於靜態工廠方法通過所傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背“開閉原則”,如何實現增加新產品而不影響已有程式碼?工廠方法模式應運而生,本文將介紹第二種工廠模式——工廠方法模式。
二、什麼是工廠方法模式
工廠方法模式(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等)中,然後根據客戶對產品的請求,對集合進行查詢。如果有滿足要求的產品物件,就直接將該產品返回客戶端;如果集合中沒有這樣的產品物件,那麼就建立一個新的滿足要求的產品物件,然後將這個物件在增加到集合中,再返回給客戶端。
- 多型性的喪失和模式的退化:如果工廠僅僅返回一個具體產品物件,便違背了工廠方法的用意,發生退化,此時就不再是工廠方法模式了。一般來說,工廠物件應當有一個抽象的父型別,如果工廠等級結構中只有一個具體工廠類的話,抽象工廠就可以省略,也將發生了退化。當只有一個具體工廠,在具體工廠中可以建立所有的產品物件,並且工廠方法設計為靜態方法時,工廠方法模式就退化成簡單工廠模式。
十、總結
- 工廠方法模式又稱為工廠模式,它屬於類建立型模式。在工廠方法模式中,工廠父類負責定義建立產品物件的公共介面,而工廠子類則負責生成具體的產品物件,這樣做的目的是將產品類的例項化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該例項化哪一個具體產品類。
- 工廠方法模式包含四個角色:抽象產品是定義產品的介面,是工廠方法模式所建立物件的超型別,即產品物件的共同父類或介面;具體產品實現了抽象產品介面,某種型別的具體產品由專門的具體工廠建立,它們之間往往一一對應;抽象工廠中聲明瞭工廠方法,用於返回一個產品,它是工廠方法模式的核心,任何在模式中建立物件的工廠類都必須實現該介面;具體工廠是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶呼叫,返回一個具體產品類的例項。
- 工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了面向物件的多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的建立,而是將具體建立工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的介面,而不負責產品類被例項化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
- 工廠方法模式的主要優點是增加新的產品類時無須修改現有系統,並封裝了產品物件的建立細節,系統具有良好的靈活性和可擴充套件性;其缺點在於增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,在一定程度上增加了系統的複雜性。
- 工廠方法模式適用情況包括:一個類不知道它所需要的物件的類;一個類通過其子類來指定建立哪個物件;將建立物件的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類建立產品子類,需要時再動態指定。