定義
define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate.
定義一個創(chuàng)建對象的接口或抽象類,但讓子類決定實例化那個具體的類。
實列
生活中,有很多的企業(yè)、店鋪、公司會為我們生產(chǎn)各種各樣的商品,作為消費者的我們只關(guān)心如何使用這些產(chǎn)品,而不需要知道它們的制作過程。
比如說,包子店它會生產(chǎn)肉包、菜包、豆腐包等各式各樣的包子,如果我們決定早上吃豆腐包中午吃豆沙包,都只需要告知店鋪便可以獲得生產(chǎn)好的包子,而不需要自己生產(chǎn)。
工廠模式中的工廠就好比上面的包子店,但在工廠模式中我們把這類相似的商品稱為產(chǎn)品,它們不僅在形態(tài)上相似,而且創(chuàng)建過程也非常相似。
故事
前不久,我入職了一家開發(fā)射擊類游戲的公司,并負責(zé)公共組件槍支庫的開發(fā)。
槍支庫中有手槍、沖鋒槍、步槍等不同類型的槍支產(chǎn)品,它們都繼承同一個抽象類Gun。
這個抽象類聲明了三個操作,一個是shoot用于射擊目標對象,一個是setBullet用于裝子彈,還有一個是load用于上膛。
公司其它部門的同事,如果要將這些產(chǎn)品應(yīng)用到不同的射擊游戲中,那么他們需要先使用new實例化對象,然后給對象裝上與之匹配的子彈并上膛,便可以調(diào)用shoot操作了。
偽代碼如下:
public class Client {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
if("HANDGUN".equals(gunName)){
Handgun handgun = new Handgun();
handgun.setBullet(new HandgunBullet());
handgun.load();
//射擊目標
handgun.shoot(target);
}else if("SMG".equals(gunName)){
SMG smg = new SMG();
smg.setBullet(new SMGBullet());
handgun.load();
//射擊目標
smg.shoot(target);
}else if("RIFLE".equals(gunName)){
Rifle smg = new Rifle();
smg.setBullet(new RifleBullet());
handgun.load();
//射擊目標
smg.shoot(target);
}
}
}
問題
從上面的代碼中我們可以看出,這款游戲會根據(jù)用戶選擇的槍支來射擊目標對象。
但是,在客戶端使用new關(guān)鍵字創(chuàng)建產(chǎn)品會存在幾個問題。首先是擴展問題,如果后續(xù)射擊游戲需要增加新的槍支,那么就要修改客戶端代碼。
其次是耦合問題,因為槍支是游戲的基礎(chǔ)類,一旦它發(fā)生變化,那么所有的客戶端都很可能都要跟著變化,比如說重命名某個槍支類的名稱。
最后是重復(fù)問題,裝彈、上膛是射擊前的初始化操作,如果槍支庫被用于多款射擊游戲,那么創(chuàng)建對象的過程就會重復(fù)。
所以,有沒有一種方式可以讓客戶端不操心產(chǎn)品是如何創(chuàng)建的?這便是工廠模式。
方案
工廠模式是一種創(chuàng)建型設(shè)計模式,它會將同類產(chǎn)品的創(chuàng)建以及初始化操作封裝到獨立的類即工廠類(Facotry)中。
工廠會向外暴露一個創(chuàng)建產(chǎn)品的操作,客戶端只需調(diào)用該操作就可以獲得指定的對象,而不需要知道產(chǎn)品是如何創(chuàng)建以及如何初始化的。
這樣,工廠便解藕了客戶端和產(chǎn)品之間的直接依賴關(guān)系,以及復(fù)用了初始化過程。
實現(xiàn)
工廠模式分為簡單工廠模式和工廠方法模式以及抽象工廠模式,下面我們來看看前兩種模式。
簡單工廠模式
首先,我們創(chuàng)建一個名為簡單工廠的類,將對象的創(chuàng)建以及初始化操作封裝起來。
public class SimpleFactory {
public static Gun create(String gunName){
Gun gun;
if("HANDGUN".equals(gunName)){
gun = new Handgun();
gun.setBullet(new HandgunBullet());
}else if("SMG".equals(gunName)){
gun = new SMG();
gun.setBullet(new SMGBullet());
}else if("RIFLE".equals(gunName)){
gun = new Rifle();
gun.setBullet(new RifleBullet());
}
gun.load();
return gun;
}
}
然后,修改客戶端代碼,將創(chuàng)建對象的任務(wù)委托給簡單工廠來創(chuàng)建,這樣客戶端就能多態(tài)地使用對象了。
public class Client1 {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
Gun gun = SimpleFactory.create(gunName);
gun.shoot(target);
}
}
可以看出,簡單工廠模式的實現(xiàn)非常的簡單,但是它沒有解決擴展的問題,當(dāng)新增產(chǎn)品時還是需要修改簡單工廠類。
工廠方法模式
在工廠方法模式中,不同的產(chǎn)品由不同的工廠類創(chuàng)建,而不是像簡單工廠一樣由一個工廠創(chuàng)建,工廠和產(chǎn)品之間通常是一對一的關(guān)系,這些工廠都繼承自一個共同的抽象類。
下面我們看看工廠模式又是如何實現(xiàn)的:
首先,我們創(chuàng)建一個抽象工廠的類,定義產(chǎn)品的創(chuàng)建流程以及聲明子類要實現(xiàn)的差異化操作。
/**抽象工廠類*/
public abstract class AbstractFactory {
/**定義創(chuàng)建對象的標準流程*/
public Gun create(){
Gun gun= doCreate();
//上膛
gun.load();
return gun;
}
/**子類差異化實現(xiàn)*/
protected abstract Gun doCreate();
}
然后,創(chuàng)建具體的工廠類實現(xiàn)差異的部分,比如,我們?yōu)槭謽寙为殑?chuàng)建一個工廠。
/**手槍工廠*/
public class HandgunFactory extends AbstractFactory{
@Override
protected Gun doCreate() {
Handgun handgun = new Handgun();
//裝子彈
handgun.setBullet(new HandgunBullet());
return handgun;
}
}
最后,我們在來看看客戶端如何使用工廠方法模式創(chuàng)建產(chǎn)品。
public class Client {
public static void main(String[] args) {
String gunName = args[0];
String target = args[1];
//具體工廠的創(chuàng)建可以使用Java的反射技術(shù)
AbstractFactory factory ;
if("HANDGUN".equals(gunName)){
factory = new HandgunFactory();
}else if("SMG".equals(gunName)){
factory = new HandgunFactory();
}else if("RIFLE".equals(gunName)){
factory = new HandgunFactory();
}
Gun handgun = factory.create();
//射擊目標
handgun.shoot(target);
}
}
很明顯,工廠方法模式?jīng)]有簡單工廠模式那樣簡單,甚至看上去比不使用工廠還復(fù)雜。
但是,用一個工廠創(chuàng)建一種產(chǎn)品,能解決擴展性方面的問題。前提是具體工廠的創(chuàng)建得使用反射,這里我們就不再深入,讀者可以自行了解一下。
結(jié)構(gòu)

產(chǎn)品(Product):這里的產(chǎn)品指的是繼承或?qū)崿F(xiàn)同一接口或父類的對象,它們同屬一個家族。
抽象工廠(AbstractFactory):它是對創(chuàng)建對象行為的抽象,聲明了可相互替代產(chǎn)品的創(chuàng)建接口。如果創(chuàng)建涉及對象初始化,那么可以在抽象類中定義共同的創(chuàng)建流程。
具體工廠(ConcreteFactory):它實現(xiàn)自抽象工廠,負責(zé)具體產(chǎn)品的創(chuàng)建。
總結(jié)
當(dāng)程序需要根據(jù)不同的條件創(chuàng)建不同的產(chǎn)品,而且產(chǎn)品的數(shù)量會持續(xù)增加時,為了避免客戶端和具體產(chǎn)品的耦合,那么我們應(yīng)該考慮使用工廠方法模式。
工廠方法模式可以解決簡單工廠解決不了的問題:擴展性問題,它可以在增加新的產(chǎn)品時不修改客戶端代碼,做到插件式地動態(tài)擴展產(chǎn)品。
擴展閱讀
架構(gòu)設(shè)計思維篇之結(jié)構(gòu)
架構(gòu)設(shè)計事務(wù)篇之Mysql事務(wù)原理