設(shè)計(jì)模式詳解之工廠模式

工廠模式顧名思義,工廠就是用來統(tǒng)一創(chuàng)建產(chǎn)品的。我們的工廠根據(jù)產(chǎn)品是具體產(chǎn)品還是工廠可以分為簡單工廠模式和工廠方法模式,根據(jù)抽象程度可以分為工廠方法模式和抽象工廠模式。工廠模式的核心本質(zhì)是:

  • 實(shí)例化對(duì)象不使用new,用工廠方法替代
  • 將選擇實(shí)現(xiàn)類,創(chuàng)建對(duì)象統(tǒng)一管理和控制。從而將調(diào)用者跟我們的實(shí)現(xiàn)類解耦。

工廠模式在我們的場景中用到的還是比較多的:

  • JDK中Calendar的getInstance方法
  • JDBC中的Connection對(duì)象的獲取
  • Spring中IOC容器創(chuàng)建管理bean對(duì)象
  • 反射中Class對(duì)象的newInstance方法

簡單工廠模式(靜態(tài)工廠模式)

用來生產(chǎn)同一等級(jí)結(jié)構(gòu)中的任意產(chǎn)品,只需要對(duì)工廠傳遞需要?jiǎng)?chuàng)建對(duì)象的類型即可。這里我們以計(jì)算器為來講解該模式:

首先是我們的運(yùn)算統(tǒng)一接口(Operation)

package simplefactory;

//運(yùn)算統(tǒng)一接口
public interface Operation {
    void operate();
}

加法操作:

package simplefactory;

public class AddOperation implements Operation{
    @Override
    public void operate() {
        System.out.println("進(jìn)行了加法操作!");
    }
}

減法操作:

package simplefactory;

public class SubOperation implements Operation{
    @Override
    public void operate() {
        System.out.println("進(jìn)行了減法操作!");
    }
}

我們定義一個(gè)簡單工廠來代替我們創(chuàng)建對(duì)象:

package simplefactory;

public class OpetationFactory {
    public static Operation createOperate(String operate){
        Operation oper = null;
        switch (operate){
            case "加法":
                oper = new AddOperation();
                break;
            case "減法":
                oper = new SubOperation();
                break;
        }
        return oper;
    }
}

我們?cè)诳蛻舳诉M(jìn)行實(shí)踐一下:

package simplefactory;

public class Client {
    public static void main(String[] args) {

        //第一個(gè)操作執(zhí)行加法操作
        Operation operation1 = OpetationFactory.createOperate("加法");
        operation1.operate();
        //第二個(gè)操作執(zhí)行減法操作
        Operation operation2 = OpetationFactory.createOperate("減法");
        operation2.operate();
    }
}

//演示結(jié)果
//進(jìn)行了加法操作!
//進(jìn)行了減法操作!

這樣我們就不需要?jiǎng)?chuàng)建對(duì)象這個(gè)過程,只需要告訴工廠我們需要什么對(duì)象就行,省略了很多的麻煩。但是如果我們這個(gè)時(shí)候要增加一個(gè)要求怎么辦呢?我們先需要實(shí)現(xiàn)操作統(tǒng)一接口,然后再在工廠里面增加我們的新操作的分支,比如我們?cè)黾右粋€(gè)乘法的操作的話。

實(shí)現(xiàn)接口:

package simplefactory;

public class MulOperation implements Operation{
    @Override
    public void operate() {
        System.out.println("進(jìn)行了乘法操作!");
    }
}

在工廠里面添加分支:

package simplefactory;

public class OpetationFactory {
    public static Operation createOperate(String operate){
        Operation oper = null;
        switch (operate){
            case "加法":
                oper = new AddOperation();
                break;
            case "減法":
                oper = new SubOperation();
                break;
            case "乘法": //新加的乘法操作分支
                oper = new MulOperation();
                break;
        }
        return oper;
    }
}

很顯然,這樣的操作如果在工程量特別大的去修改代碼是特別麻煩的,而且主要的是,這不符合我們的開閉原則(對(duì)修改關(guān)閉,對(duì)擴(kuò)展開放)。

所以我們是有辦法來關(guān)閉修改操作的——反射,下面代碼我們對(duì)創(chuàng)建工廠進(jìn)行重構(gòu)之后(T表示只要實(shí)現(xiàn)了Operaton都可以當(dāng)作接口,是JDK1.5之后的新特性——泛型,減少了對(duì)象之間的轉(zhuǎn)換)

package simplefactory;

public class OpetationFactory {

    public static <T extends Operation> T createOperate(Class<T> c){
        Operation oper = null;
        try{
            oper = (T) Class.forName(c.getName()).newInstance();
        }catch (Exception e){
            System.out.println("沒有該操作!");
        }
        return (T) oper;
    }
}

這個(gè)時(shí)候如果需要添加新的操作的時(shí)候,我們只需要?jiǎng)?chuàng)建實(shí)現(xiàn)類即可,然后在客戶端就可以使用了,關(guān)閉了對(duì)創(chuàng)建工廠的修改。符合了我們的開閉原則。

package simplefactory;

public class Client {
    public static void main(String[] args) {

        //第一個(gè)操作執(zhí)行加法操作
        Operation operation1 = OpetationFactory.createOperate(AddOperation.class);
        operation1.operate();
        //第二個(gè)操作執(zhí)行減法操作
        Operation operation2 = OpetationFactory.createOperate(SubOperation.class);
        operation2.operate();
        //第三個(gè)操作為我們的乘法操作
        Operation operation3 = OpetationFactory.createOperate(MulOperation.class);
        operation3.operate();
    }
}

控制臺(tái)結(jié)果:

進(jìn)行了加法操作!
進(jìn)行了減法操作!
進(jìn)行了乘法操作!

Process finished with exit code 0

對(duì)簡單工廠模式的優(yōu)化還有其他方式,可以看看這篇博客Java簡單工廠模式以及來自lambda的優(yōu)化

工廠方法模式

工廠方法是對(duì)簡單工廠模式進(jìn)一步的修改,是在不修改已有類的前提下,通過增加新的工廠類實(shí)現(xiàn)擴(kuò)展。可以說是對(duì)沒有優(yōu)化前的簡單工廠模式的一種優(yōu)化,貼合了開閉原則。

我們通過UML圖可以清晰認(rèn)識(shí):

我們操作的接口和實(shí)現(xiàn)類不變,只不過我們把原來在一個(gè)工廠生產(chǎn)對(duì)象的的工廠變成了每一個(gè)實(shí)現(xiàn)類都有自己的生產(chǎn)工廠了。這樣我們?nèi)绻枰偬砑有碌牟僮鞯脑?,我們只需要添加一個(gè)實(shí)現(xiàn)類和一個(gè)工廠就行了,非常符合我們的開閉原則。

定義我們工廠統(tǒng)一接口的抽象類:

package factorymethod;

//工廠統(tǒng)一接口
public interface OperationFactory {
    Operation doingOperate();
}

然后是我們加法的工廠:

package factorymethod;

public class AddOperationFactory implements OperationFactory{
    @Override
    public Operation doingOperate() {
        return new AddOperation();
    }
}

減法的工廠:

package factorymethod;

public class SubOperationFactory implements OperationFactory {
    @Override
    public Operation doingOperate() {
        return new SubOperation();
    }
}

我們的客戶端:

package factorymethod;

public class Client {
    public static void main(String[] args) {

        OperationFactory factoryAdd = new AddOperationFactory();
        OperationFactory factorySub = new SubOperationFactory();
        // 第一個(gè)操作為加法操作
        Operation operation1 = factoryAdd.doingOperate();
        operation1.operate();   //輸出:進(jìn)行了加法操作
        // 第二個(gè)操作為減法操作
        Operation operation2 = factorySub.doingOperate();
        operation2.operate();  //輸出:進(jìn)行了減法操作
    }
}

抽象工廠模式

抽象工廠總的來說,提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無需指定它們具體的類。就是圍繞一個(gè)超級(jí)工廠來創(chuàng)建其他工廠,該超級(jí)工廠又稱為其他工廠的工廠。

這里我再以計(jì)算器的操作來舉例有點(diǎn)不合適,網(wǎng)上最多的就是手機(jī)和電腦品牌的舉例。因?yàn)榭梢员容^清晰理解抽象工廠,我們不是增加一個(gè)產(chǎn)品,而是增加一個(gè)產(chǎn)品族。如果我們只是單一的一個(gè)手機(jī)可以使用上面的工廠方法來創(chuàng)建,但是如果多了一個(gè)電腦呢?可以復(fù)制一下手機(jī)工廠方法的代碼,修改一下名字就可以使用,但是這很明顯的就是重復(fù)代碼了吧。我們是無論如何也不能忍受的,所以就有了抽象工廠模式。

首先我們先來定義一個(gè)手機(jī)的統(tǒng)一接口

package abstractfactory;

public interface Phone {
    void make();
}

實(shí)現(xiàn)華為和小米的Phone實(shí)現(xiàn)類:

package abstractfactory;

public class HuaWeiPhone implements Phone {
    @Override
    public void makePhone() {
        System.out.println("生產(chǎn)的是華為手機(jī)!");
    }
}
package abstractfactory;

public class XiaoMiPhone implements Phone{
    @Override
    public void makePhone() {
        System.out.println("生產(chǎn)的是小米手機(jī)!");
    }
}

定義一個(gè)電腦的統(tǒng)一接口

package abstractfactory;

public interface Computer {
    void makeComputer();
}

實(shí)現(xiàn)華為和小米的Computer實(shí)現(xiàn)類:

package abstractfactory;

public class HuaWeiComputer implements Computer {
    @Override
    public void makeComputer() {
        System.out.println("生產(chǎn)的是華為電腦!");
    }
}
package abstractfactory;

public class XiaoMiComputer implements Computer{
    @Override
    public void makeComputer() {
        System.out.println("生產(chǎn)的是小米電腦!");
    }
}

然后就是我們的抽象工廠了,因?yàn)槲覀儾还苁切∶坠S還是華為工廠都需要生產(chǎn)手機(jī)和電腦,所以我們可以說這就是一個(gè)產(chǎn)品族。

package abstractfactory;

public interface AbstractFactory {
    Phone makePhone();
    Computer makeComputer();
}

然后我們實(shí)現(xiàn)抽象工廠的接口,有小米工廠和華為工廠

package abstractfactory;

public class XiaoMiFactory implements AbstractFactory {
    @Override
    public Phone makePhone() {
        return new XiaoMiPhone();
    }

    @Override
    public Computer makeComputer() {
        return new XiaoMiComputer();
    }
}
package abstractfactory;

public class HuaWeiFactory implements AbstractFactory {
    @Override
    public Phone makePhone() {
        return new HuaWeiPhone();
    }

    @Override
    public Computer makeComputer() {
        return new HuaWeiComputer();
    }
}

在我們的客戶端使用:

package abstractfactory;

public class Client {
    public static void main(String[] args) {
        AbstractFactory xiaoMiFactory = new XiaoMiFactory();
        Phone xiaoMiPhone = xiaoMiFactory.makePhone();
        xiaoMiPhone.makePhone();  //輸出:生產(chǎn)的是小米手機(jī)!
        Computer xiaoMiComputer = xiaoMiFactory.makeComputer();
        xiaoMiComputer.makeComputer(); //輸出:生產(chǎn)的是小米電腦!

        AbstractFactory huaWeiFactory = new HuaWeiFactory();
        Phone huaWeiPhone = huaWeiFactory.makePhone();
        huaWeiPhone.makePhone(); // 輸出:生產(chǎn)的是華為手機(jī)!
        Computer huaWeiComputer = huaWeiFactory.makeComputer();
        huaWeiComputer.makeComputer(); //輸出:生產(chǎn)的是華為電腦!
    }
}

關(guān)于我們的抽象工廠的適用場景:

  • 客戶端不依賴產(chǎn)品類實(shí)例如何被創(chuàng)建,實(shí)現(xiàn)等細(xì)節(jié)
  • 強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品對(duì)象(屬于同一產(chǎn)品族)一起使用創(chuàng)建對(duì)象需要大量的重復(fù)代碼
  • 提供一個(gè)產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn),從而使得客戶端不依賴于具體的實(shí)現(xiàn)

優(yōu)點(diǎn):

  • 具體產(chǎn)品在應(yīng)用層的代碼隔離,無需關(guān)心創(chuàng)建的細(xì)節(jié)
  • 將一個(gè)系列的產(chǎn)品統(tǒng)一到一起創(chuàng)建

缺點(diǎn):

  • 規(guī)定了所有可能被創(chuàng)建的產(chǎn)品集合,產(chǎn)品簇中擴(kuò)展新的產(chǎn)品困難(注意是產(chǎn)品擴(kuò)展族而不是產(chǎn)品等級(jí)。比如我們這個(gè)時(shí)候要添加一個(gè)一加品牌,只要擁有相同的產(chǎn)品簇,我們只要實(shí)現(xiàn)產(chǎn)品接口有實(shí)現(xiàn)類然后添加一個(gè)工廠生產(chǎn)實(shí)例就可以實(shí)現(xiàn)??梢哉f是橫向擴(kuò)展,符合開閉原則。但是如果這時(shí)候要添加一個(gè)充電寶的產(chǎn)品,就需要修改里面的大量代碼,極其復(fù)雜,所以需要在剛開始就要設(shè)計(jì)好。)
  • 增加了系統(tǒng)的抽象性和理解難度

總結(jié)

工廠模式的三種模式有各自的應(yīng)用場景,無論哪種模式,只要能解決問題就是好用的。

參考資料

《大話設(shè)計(jì)模式》

《設(shè)計(jì)模式之禪(第二版)》

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

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

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