生活中的設(shè)計模式之抽象工廠模式

定義

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)

avatar

產(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è)計思維篇之概念

架構(gòu)設(shè)計容錯篇之重試

架構(gòu)設(shè)計容錯篇之熔斷

架構(gòu)設(shè)計容錯篇之限流

架構(gòu)設(shè)計事務(wù)篇之Mysql事務(wù)原理

架構(gòu)設(shè)計事務(wù)篇之CAP定理

架構(gòu)設(shè)計事務(wù)篇之分布式事務(wù)

架構(gòu)設(shè)計消息篇之消息丟失

架構(gòu)設(shè)計消息篇之保證消息順序性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容