設(shè)計(jì)模式之工廠模式
一、工廠模式簡(jiǎn)介
1、使用工廠模式的好處
工廠模式是創(chuàng)建型模式中最典型的模式,主要作用是用來(lái)創(chuàng)建對(duì)象,使用工廠模式替代new操作有如下幾個(gè)好處:
1:解耦,把創(chuàng)建對(duì)象的工作和使用對(duì)象的工作分開(kāi),比如我們想使用對(duì)象A,我們無(wú)需關(guān)心對(duì)象A是如何創(chuàng)建的,直接去工廠獲取就行。
2:對(duì)于構(gòu)造函數(shù)很復(fù)雜的對(duì)象,可以很好地對(duì)外層屏蔽代碼的復(fù)雜性。
3:把初始化實(shí)例的工作放到工廠里進(jìn)行統(tǒng)一管理,使代碼更容易維護(hù)。
4:修改代碼時(shí)不會(huì)引起太大的變動(dòng),如果想改代碼的話,只需要改一處即可,良好的擴(kuò)展性。
5:減少代碼量,如果構(gòu)造方法比較復(fù)雜,有很多很多個(gè)構(gòu)造函數(shù),需要一連串的代碼創(chuàng)建對(duì)象,如果使用new就會(huì)造成很多重復(fù)的代碼。
2、用一個(gè)例子說(shuō)明使用工廠模式相對(duì)于new操作的優(yōu)勢(shì)
下面我們分別從使用new創(chuàng)建對(duì)象和工廠模式兩種方面講解,幫助大家理解工廠模式的優(yōu)勢(shì)。
需求1:有三個(gè)用戶,需要定制相同的藍(lán)莓黑巧動(dòng)物奶油做的蛋糕。使用new創(chuàng)建對(duì)象:
// 蛋糕類
public class Cake {
private String fruit;// 配料:水果
private String chocolate;// 配料:巧克力
private String cream;// 配料:奶油
public Cake(String fruit, String chocolate, String cream){
this.fruit = fruit;
this.chocolate = chocolate;
this.cream = cream;
}
}
public class Test1 {
public static void main(String[] args) {
// 用戶A訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
Cake cake1 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油");
// 用戶B訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
Cake cake2 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油");
// 用戶C訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
Cake cake3 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油");
}
}
工廠模式:
// 蛋糕工廠
public class CakeFactory {
public Cake createCake(){
return new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油");
}
}
public class Test2 {
public static void main(String[] args) {
CakeFactory cakeFactory = new CakeFactory();
// 用戶A訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
cakeFactory.createCake();
// 用戶B訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
cakeFactory.createCake();
// 用戶C訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油做的蛋糕
cakeFactory.createCake();
}
}
需求2;這三個(gè)用戶想要在蛋糕中加入一種堅(jiān)果,如果使用new創(chuàng)建對(duì)象,需要修改三個(gè)new操作,而使用工廠模式,只需修改工廠中的new操作即可,改動(dòng)更少,帶來(lái)錯(cuò)誤的可能性就越少。 使用new創(chuàng)建對(duì)象:
// 蛋糕類
public class Cake {
private String fruit;// 配料:水果
private String chocolate;// 配料:巧克力
private String cream;// 配料:奶油
private String nut;// 配料:堅(jiān)果
public Cake(String fruit, String chocolate, String cream, String nut){
this.fruit = fruit;
this.chocolate = chocolate;
this.cream = cream;
this.nut = nut;
}
}
public class Test1 {
public static void main(String[] args) {
// 用戶A訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
Cake cake1 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油", "榛子");
// 用戶B訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
Cake cake2 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油", "榛子");
// 用戶C訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
Cake cake3 = new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油", "榛子");
}
}
工廠模式:
// 蛋糕工廠
public class CakeFactory {
public Cake createCake(){
return new Cake("藍(lán)莓", "黑巧克力", "動(dòng)物奶油", "榛子");
}
}
public class Test2 {
public static void main(String[] args) {
CakeFactory cakeFactory = new CakeFactory();
// 用戶A訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
cakeFactory.createCake();
// 用戶B訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
cakeFactory.createCake();
// 用戶C訂了一個(gè)藍(lán)莓黑巧動(dòng)物奶油榛子做的蛋糕
cakeFactory.createCake();
}
}
3、工廠模式分類
工廠模式,包含三種情況,簡(jiǎn)單工廠、工廠方法、抽象工廠,下面我們就來(lái)一一介紹一下。
我相信大家都有這樣的困惑,目前我所在的項(xiàng)目都在程序開(kāi)發(fā)的過(guò)程中,還是有很多的new()操作出現(xiàn)在程序中,并沒(méi)有通過(guò)工廠來(lái)創(chuàng)建對(duì)象,一方面可能是因?yàn)槲覀冏陨肀容^懶,不規(guī)范項(xiàng)目的編碼形式,另外一方面也是由于項(xiàng)目的進(jìn)度比較緊,沒(méi)有那么多的時(shí)間去完成工廠的統(tǒng)一創(chuàng)建。
二、簡(jiǎn)單工廠
我們將用幾個(gè)例子來(lái)逐步講解簡(jiǎn)單工廠、工廠方法、抽象工廠,下面我們就開(kāi)始吧。
1、簡(jiǎn)單工廠示例
小明在老家開(kāi)了一家飲品店,提供多種飲品,當(dāng)有顧客來(lái)店里,只需要和服務(wù)員說(shuō)他想喝哪種飲料,服務(wù)員便可提供哪種飲料,這就是簡(jiǎn)單工廠模式,服務(wù)員就是這個(gè)簡(jiǎn)單工廠。
// 抽象飲料
public abstract class Drink {
public abstract void desc();
}
// 蘋(píng)果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯蘋(píng)果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯西瓜汁!");
}
}
// 飲料工廠
public class DrinkFactory {
// name:飲料名字
public Drink getDrink(String name){
if("apple".equals(name)){
return new AppleDrink();
}else if("watermelon".equals(name)){
return new WatermelonDrink();
}else if("grape".equals(name)){
return new GrapeDrink();
}
return null;
}
}
// 客戶端
public class Client {
public static void main(String[] args) {
DrinkFactory drinkFactory = new DrinkFactory();
// 顧客想要蘋(píng)果汁
Drink appleDrink = drinkFactory.getDrink("apple");
appleDrink.desc();
// 顧客想要葡萄汁
Drink grapeDrink = drinkFactory.getDrink("grape");
grapeDrink.desc();
}
}
2、簡(jiǎn)單工廠的適用場(chǎng)景
(1)、工廠類負(fù)責(zé)創(chuàng)建的對(duì)象比較少。
(2)、客戶端(應(yīng)用層)只知道傳入工廠類的參數(shù),對(duì)于如何創(chuàng)建對(duì)象(邏輯)不關(guān)心。
3、簡(jiǎn)單工廠的優(yōu)點(diǎn)
只需要傳入一個(gè)正確的參數(shù),就可以獲取你所需要的對(duì)象,而無(wú)需知道其創(chuàng)建細(xì)節(jié),工廠類要有必要的判斷邏輯(if else),可以決定在什么時(shí)候創(chuàng)建哪一個(gè)產(chǎn)品的實(shí)例,客戶端可以免除直接創(chuàng)建對(duì)象的責(zé)任。
4、簡(jiǎn)單工廠的缺點(diǎn)
(1)、工廠類的職責(zé)相對(duì)較重,增加新的產(chǎn)品,需要修改工廠類的判斷邏輯(增加if else分支),修改就意味著引入風(fēng)險(xiǎn),這違反了開(kāi)閉原則。
(2)、當(dāng)產(chǎn)品很多的時(shí)候,這個(gè)工廠類就會(huì)很復(fù)雜,不利于維護(hù)和擴(kuò)展,代碼無(wú)法閱讀,很難交接。
(3)、無(wú)法形成基于繼承的等級(jí)結(jié)構(gòu)。在工廠方法和抽象工廠中,會(huì)講到產(chǎn)品族和產(chǎn)品等級(jí)結(jié)構(gòu),再了解什么是基于繼承的等級(jí)結(jié)構(gòu)。
三、工廠方法
1、工廠方法示例
后來(lái)店里飲料品種越來(lái)越多,每增加一款飲料都需要讓服務(wù)員知曉,否則顧客說(shuō)的時(shí)候,服務(wù)員就無(wú)法提供(這就是簡(jiǎn)單工廠中所說(shuō)的對(duì)工廠類的修改)。
于是,小明直接買(mǎi)了飲料機(jī),每種飲料都有自己的飲料機(jī)(這個(gè)飲料機(jī)就是工廠方法中的產(chǎn)品工廠,每個(gè)產(chǎn)品都有自己的工廠),顧客想喝什么,就自己去飲料機(jī)取,飲品店以后想增加飲料,只要增加飲料機(jī)即可(工廠方法相對(duì)于簡(jiǎn)單工廠,更加易于擴(kuò)展)。
// 抽象飲料
public abstract class Drink {
public abstract void desc();
}
// 蘋(píng)果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯蘋(píng)果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯西瓜汁!");
}
}
// 抽象飲料工廠
public abstract class DrinkFactory {
public abstract Drink getDrink();
}
// 蘋(píng)果汁飲料工廠(蘋(píng)果汁飲料機(jī))
public class AppleDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new AppleDrink();
}
}
// 葡萄汁飲料工廠
public class GrapeDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new GrapeDrink();
}
}
// 西瓜汁飲料工廠
public class WatermelonDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new WatermelonDrink();
}
}
// 客戶端
public class Client {
public static void main(String[] args) {
// 顧客想要蘋(píng)果汁
DrinkFactory appleDrinkFactory = new AppleDrinkFactory();
Drink appleDrink = appDrinkFactory.getDrink();
appleDrink.desc();
// 顧客想要葡萄汁
DrinkFactory grapeDrinkFactory = new GrapeDrinkFactory();
Drink grapeDrink = grapeDrinkFactory.getDrink();
grapeDrink.desc();
}
}
2、工廠方法和簡(jiǎn)單工廠的區(qū)別
簡(jiǎn)單工廠:所有的產(chǎn)品都在一個(gè)工廠中生產(chǎn),在工廠中使用if else根據(jù)傳入?yún)?shù)判斷生產(chǎn)哪種產(chǎn)品,當(dāng)增加一種產(chǎn)品的時(shí)候,就需要修改工廠的if else邏輯。
工廠方法:每種產(chǎn)品都有自己的工廠,當(dāng)需要增加一種產(chǎn)品時(shí),無(wú)需修改現(xiàn)有的工廠,只需要增加一種工廠即可。遵循了開(kāi)閉原則,對(duì)于擴(kuò)展是開(kāi)放的,對(duì)于修改是關(guān)閉的。
3、工廠方法的優(yōu)點(diǎn)
(1)、用戶只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無(wú)需關(guān)心產(chǎn)品的創(chuàng)建細(xì)節(jié)。
(2)、加入新產(chǎn)品時(shí),只需要再增加對(duì)應(yīng)的工廠,而不用修改已有的工廠,符合開(kāi)閉原則,提高擴(kuò)展性。
4、工廠方法的缺點(diǎn)
類爆炸,因?yàn)槊總€(gè)產(chǎn)品都要有一個(gè)工廠,如果產(chǎn)品很多,那就會(huì)有很多的工廠。用上面的例子來(lái)描述,如果后續(xù)飲品店出了成百上千種飲品,就要在店里放成百上千臺(tái)飲料機(jī),店里放不下啊。。。
四、抽象工廠
1、抽象工廠示例
至于抽象工廠模式,可以用套餐來(lái)理解。小明在后期,除了提供飲品,還給每種飲品搭配了合適的甜點(diǎn),向顧客提供下午茶套餐,以此來(lái)提高銷(xiāo)售量。
所以飲品店制定了一套標(biāo)準(zhǔn)(抽象工廠,具體工廠需要遵循的工廠接口),所有的套餐(具體工廠)必須遵循該標(biāo)準(zhǔn),比如這個(gè)抽象標(biāo)準(zhǔn)可能是:1、需要有一杯果汁。2、需要有一塊甜點(diǎn)。于是有了第一個(gè)套餐(具體工廠):西瓜汁 + 泡芙,我們稱之為套餐A,那顧客點(diǎn)套餐A,即可獲得剛才所說(shuō)產(chǎn)品。當(dāng)然這里面的西瓜汁、泡芙(具體產(chǎn)品對(duì)象),也需要遵守相應(yīng)的產(chǎn)品規(guī)則(產(chǎn)品接口),以保證顧客能吃到放心食品。
產(chǎn)品接口如下:
// 飲料抽象類
public abstract class Drink {
public abstract void desc();
}
// 甜點(diǎn)抽象類
public abstract class Dessert {
public abstract void desc();
}
具體產(chǎn)品對(duì)象如下:
// 蘋(píng)果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯蘋(píng)果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("這是一杯西瓜汁!");
}
}
// 泡芙
public class Puff extends Dessert {
@Override
public void desc() {
System.out.println("這是一塊泡芙!");
}
}
// 慕斯蛋糕
public class MousseCake extends Dessert {
@Override
public void desc() {
System.out.println("這是一塊慕斯蛋糕!");
}
}
// 提拉米蘇蛋糕
public class Tiramisu extends Dessert {
@Override
public void desc() {
System.out.println("這是一塊提拉米蘇蛋糕!");
}
}
抽象工廠如下:
// 下午茶套餐工廠
public interface SetFactory {
Drink getDrink();
Dessert getDessert();
}
具體工廠如下:
/**
* 套餐A的工廠
* 套餐A提供西瓜汁 + 泡芙
*/
public class ASetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new WatermelonDrink();
}
@Override
public Dessert getDessert() {
return new Puff();
}
}
/**
* 套餐B的工廠
* 套餐B提供葡萄汁 + 慕斯蛋糕
*/
public class BSetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new GrapeDrink();
}
@Override
public Dessert getDessert() {
return new MousseCake();
}
}
/**
* 套餐C的工廠
* 套餐C提供蘋(píng)果汁 + 提拉米蘇蛋糕
*/
public class CSetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new AppleDrink();
}
@Override
public Dessert getDessert() {
return new Tiramisu();
}
}
客戶端如下:
public class Client {
public static void main(String[] args) {
// 顧客想要一份A套餐
System.out.println("------A套餐-------");
SetFactory aSetFactory = new ASetFactory();
Drink aDrink = aSetFactory.getDrink();
Dessert aDessert = aSetFactory.getDessert();
aDrink.desc();
aDessert.desc();
// 顧客想要一份B套餐
System.out.println("------B套餐-------");
SetFactory bSetFactory = new BSetFactory();
Drink bDrink = bSetFactory.getDrink();
Dessert bDessert = bSetFactory.getDessert();
bDrink.desc();
bDessert.desc();
}
}
在這里我們看到,對(duì)于抽象工廠是無(wú)需指定具體的果汁和甜點(diǎn),但是這些果汁、甜點(diǎn)卻是相互依賴的,這也是抽象工廠的意圖:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無(wú)需指定它們具體的類。
3、抽象工廠的優(yōu)點(diǎn)
將一系列的產(chǎn)品統(tǒng)一到一起創(chuàng)建。
4、抽象工廠的缺點(diǎn)
(1)、規(guī)定了所有可能被創(chuàng)建的產(chǎn)品集合,當(dāng)產(chǎn)品集合中需要擴(kuò)展新的產(chǎn)品時(shí),需要修改抽象工廠的接口,違反了開(kāi)閉原則,比如上面的例子,當(dāng)我們需要在套餐中加入一份小吃時(shí),就需要修改抽象工廠和所有的具體工廠,改動(dòng)很大。
(2)、增加了系統(tǒng)的抽象性和理解難度。
5、適用場(chǎng)景
強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品組成一個(gè)產(chǎn)品集合一起使用時(shí),此時(shí)使用抽象工廠模式。
五:工廠模式總結(jié)
我們結(jié)合上面飲品店的例子,來(lái)總結(jié)一下簡(jiǎn)單工廠、工廠方法、抽象工廠這三種工廠有什么不同吧,方便大家更好地記憶。
簡(jiǎn)單工廠,就是所有的產(chǎn)品都在這個(gè)工廠中生產(chǎn),用戶需要告訴工廠他需要哪個(gè)產(chǎn)品,工廠才能給他對(duì)應(yīng)的產(chǎn)品。

簡(jiǎn)單工廠
工廠方法,就是每種產(chǎn)品都有自己的工廠,用戶想要哪個(gè)產(chǎn)品,直接去對(duì)應(yīng)的工廠取即可。

工廠方法
抽象工廠,相當(dāng)于對(duì)所有的產(chǎn)品進(jìn)行分類(分類的維度根據(jù)業(yè)務(wù)場(chǎng)景來(lái)決定),同一種類的產(chǎn)品在同一工廠生產(chǎn),用戶想要哪一種類的產(chǎn)品,直接去對(duì)應(yīng)的工廠取即可。

標(biāo)注:圖中綠色方框表示工廠,橘色方框表示產(chǎn)品。
