一篇搞定工廠模式【簡單工廠、工廠方法模式、抽象工廠模式】


一 為什么要用工廠模式

之前講解 Spring 的依賴注入的文章時,我們就已經有提到過工廠這種設計模式,我們直接先通過一個例子來看一下究竟工廠模式能用來做什么?

【萬字長文】Spring框架 層層遞進輕松入門 (IOC和DI)

首先,我們簡單的模擬一個對賬戶進行添加的操作,我們先采用我們以前常常使用的方式進行模擬,然后再給出改進方案

(一) 舉一個模擬 Spring IOC 的例子

(1) 以前的程序

首先,按照我們常規的方式先模擬,我們先將一套基本流程走下來

A:Service 層

/**
 * 賬戶業務層接口
 */
public interface AccountService {
    void addAccount();
}

/**
 * 賬戶業務層實現類
 */
public class AccountServiceImpl implements AccountService {
	
	private AccountDao accountDao = new AccountDaoImpl();
	
    public void addAccount() {
        accountDao.addAccount();
    }
}

B:Dao 層

/**
 * 賬戶持久層接口
 */
public interface AccountDao {
    void addAccount();
}

/**
 * 賬戶持久層實現類
 */
public class AccountDaoImpl implements AccountDao {

    public void addAccount() {
        System.out.println("添加用戶成功!");
    }
}

C:調用

由於,我們創建的Maven工程並不是一個web工程,我們也只是為了簡單模擬,所以在這里,創建了一個 Client 類,作為客戶端,來測試我們的方法

public class Client {
    public static void main(String[] args) {
		AccountService  as = new AccountServiceImpl();
		as.addAccount();
    }
}

運行的結果,就是在屏幕上輸出一個添加用戶成功的字樣

D:分析:new 的問題

上面的這段代碼,應該是比較簡單也容易想到的一種實現方式了,但是它的耦合性卻是很高的,其中這兩句代碼,就是造成耦合性高的根由,因為業務層(service)調用持久層(dao),這個時候業務層將很大的依賴於持久層的接口(AccountDao)和實現類(AccountDaoImpl)

private AccountDao accountDao = new AccountDaoImpl();

AccountService as = new AccountServiceImpl();

這種通過 new 對象的方式,使得不同類之間的依賴性大大增強,其中一個類的問題,就會直接導致出現全局的問題,如果我們將被調用的方法進行錯誤的修改,或者說刪掉某一個類,執行的結果就是:

編譯期就出現了錯誤,而我們作為一個開發者,我們應該努力讓程序在編譯期不依賴,而運行時才可以有一些必要的依賴(依賴是不可能完全消除的)

所以,我們應該想辦法進行解耦,要解耦就要使調用者被調用者之間沒有什么直接的聯系,那么工廠模式就可以幫助我們很好的解決這個問題

(2) 工廠模式改進

A:BeanFactory

具體怎么實現呢?在這里可以將 serivice 和 dao 均配置到配置文件中去(xml/properties),通過一個類讀取配置文件中的內容,並使用反射技術創建對象,然后存起來,完成這個操作的類就是我們的工廠

注:在這里我們使用了 properties ,主要是為了實現方便,xml還涉及到解析的一些代碼,相對麻煩一些,不過我們下面要說的 Spring 就是使用了 xml做配置文件

  • bean.properties:先寫好配置文件,將 service 和 dao 以 key=value 的格式配置好
accountService=cn.ideal.service.impl.AccountServiceImpl
accountDao=cn.ideal.dao.impl.AccountDaoImpl
  • BeanFactory
public class BeanFactory {
    //定義一個Properties對象
    private static Properties properties;
    //使用靜態代碼塊為Properties對象賦值
    static {
        try{
            //實例化對象
            properties = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(in);
        }catch (Exception e){
            throw  new ExceptionInInitializerError("初始化properties失敗");
        }
    }  
}

簡單的解釋一下這部分代碼(當然還沒寫完):首先就是要將配置文件中的內容讀入,這里通過類加載器的方式操作,讀入一個流文件,然后從中讀取鍵值對,由於只需要執一次,所以放在靜態代碼塊中,又因為 properties 對象在后面的方法中還要用,所以寫在成員的位置

接着在 BeanFactory 中繼續編寫一個 getBean 方法其中有兩句核心代碼的意義就是:

  • 通過方法參數中傳入的字符串,找到對應的全類名路徑,實際上也就是通過剛才獲取到的配置內容,通過key 找到 value值

  • 下一句就是通過 Class 的加載方法加載這個類,實例化后返回
public static Object getBean(String beanName){
    Object bean = null;

    try {
        //根據key獲取value
        String beanPath = properties.getProperty(beanName);
        bean = Class.forName(beanPath).newInstance();
    }catch (Exception e){
        e.printStackTrace();
    }
    return bean;
}

B:測試代碼:

public class Client {
    public static void main(String[] args) {
        AccountService as = 					   (AccountService)BeanFactory.getBean("accountService");
        as.addAccount();
    }
}

C:執行效果:

當我們按照同樣的操作,刪除掉被調用的 dao 的實現類,可以看到,這時候編譯期錯誤已經消失了,而報出來的只是一個運行時異常,這樣就解決了前面所思考的問題

我們應該努力讓程序在編譯期不依賴,而運行時才可以有一些必要的依賴(依賴是不可能完全消除的)

(3) 小總結:

為什么使用工廠模式替代了 new 的方式?

打個比方,在你的程序中,如果一段時間后,你發現在你 new 的這個對象中存在着bug或者不合理的地方,或者說你甚至想換一個持久層的框架,這種情況下,沒辦法,只能修改源碼了,然后重新編譯,部署,但是如果你使用工廠模式,你只需要重新將想修改的類,單獨寫好,編譯后放到文件中去,只需要修改一下配置文件就可以了

我分享下我個人精簡下的理解就是:

【new 對象依賴的是具體事物,而不 new 則是依賴抽象事物】

Break it down:

  • 依賴具體事物,這個很好理解,你依賴的是一個具體的,實實在在內容,它與你系相關,所以有什么問題,都是連環的,可能為了某個點,我們需要修改 N 個地方,絕望
  • 依賴抽象事物,你所調用的並不是一個直接就可以觸手可及的東西,是一個抽象的概念,所以不存在上面那種情況下的連環反應

二 三種工廠模式

看完前面的例子,我想大家已經已經對工廠模式有了一個非常直觀的認識了

說白了,工廠模式就是使用一種手段,代替了 new 這個操作

以往想要獲取一個實例的時候要 new 出來,但是這種方式耦合性就會很高,我們要盡量的減少這種可避免的耦合負擔,所以工廠模式就來了

工廠就是在調用者和被調用者之間起一個連接樞紐的作用,調用者和被調用者都只與工廠進行聯系,從而減少了兩者之間直接的依賴

工廠模式一共有三種 ① 簡單工廠模式,② 工廠方法模式 ③ 抽象工廠模式

下面我們一個一個來說

(一) 簡單工廠模式

(1) 實現

下面我們以一個車的例子來講,首先我們有一個抽象的 Car 類

public abstract class Car {
    // 任何汽車都會跑
    public abstract void run();
}

接着就是它的子類,我們先來兩個,一個寶馬類,一個奔馳類(為閱讀方便寫成了拼音命名,請勿模仿,不建議)

public class BaoMa extends Car {
    @Override
    public void run() {
        System.out.println("【寶馬】在路上跑");
    }
}
public class BenChi extends Car {
    @Override
    public void run() {
        System.out.println("【奔馳】在路上跑");
    }
}

那如果我想要實例化這個類,實際上最原始的寫法可以這樣(也就是直接 new 出來)

public class Test {
    public static void main(String[] args) {
        Car baoMa = new BaoMa();
        baoMa.run();
        Car benChi = new BenChi();
        benChi.run();
    }
}

如果使用簡單工廠模式,就需要創建一個專門的工廠類,用來實例化對象

public class CarFactory {
    public static Car createCar(String type) {
        if ("寶馬".equals(type)) {
            return new BaoMa();
        } else if ("奔馳".equals(type)) {
            return new BenChi();
        } else {
            return null;
        }
    }
}

真正去調用的時候,我只需要傳入一個正確的參數,通過 CarFactory 創建出想要的東西就可以了,具體怎么去創建就不需要調用者操心了

public class Test {
    public static void main(String[] args) {
		Car baoMa = CarFactory.createCar("寶馬");
        baoMa.run();
        Car benChi = CarFactory.createCar("奔馳");
        benChi.run();
    }
}

(2) 優缺點

先說一下優點

簡單工廠模式的優點就在於其工廠類中含有必要的邏輯判斷(例如 CarFactory 中判斷是寶馬還是奔馳),客戶端只需要通過傳入參數(例如傳入 “寶馬”),動態的實例化想要的類,客戶端就免去了直接創建產品的職責,去除了與具體產品的依賴(都不需要知道具體類名了,反正我不負責創建)

但是其缺點也很明顯

簡單工廠模式的工廠類職責過於繁重,違背了高聚合原則,同時其內容多的情況下,邏輯太復雜。最關鍵的是,當我想要增加一個新的內容的時候,例如增加一個保時捷,我就不得不去修改 CarFactory 工廠類中的代碼,這很顯然違背了 “開閉原則”

所以,工廠模式他就來了

(二) 工廠模式

(1) 實現

依舊是一個汽車抽象類,一個寶馬類和一個奔馳類是其子類

public abstract class Car {
    // 任何汽車都會跑
    public abstract void run();
}
public class BaoMa extends Car {
    @Override
    public void run() {
        System.out.println("【寶馬】在路上跑");
    }
}
public class BenChi extends Car {
    @Override
    public void run() {
        System.out.println("【奔馳】在路上跑");
    }
}

如果是簡單工廠類,就會 有一個總的工廠類來實例化對象,為了解決其缺點,工廠類首先需要創建一個汽車工廠接口類

public interface CarFactory {
    // 可以獲取任何車
    Car createCar();
}

然后寶馬和奔馳類分別實現它,內容就是創建一個對應寶馬或者奔馳(實例化寶馬類或者奔馳類)

public class BaoMaFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new BaoMa();
    }
}
public class BenChiFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new BenChi();
    }
}

想要獲取車的時候,只需要通過多態創建出想要獲得的那種車的工廠,然后通過工廠再創建出對應的車,例如我分別拿到奔馳和寶馬就可以這樣做:

public class Test {
    public static void main(String[] args) {
        // 先去奔馳工廠拿到一台奔馳
        CarFactory benChiFactory = new BenChiFactory();
        // 4S店拿到一台奔馳,給了你
        Car benChi = benChiFactory.createCar();
        benChi.run();

        // 先去寶馬工廠拿到一台寶馬
        CarFactory baoMaFactory = new BaoMaFactory();
        // 4S店拿到一台寶馬,給了你
        Car baoMa = baoMaFactory.createCar();
        baoMa.run();
    }
}

這種情況下,如果我還想要增加一台保時捷類型的車,創建出對應的保時捷類(繼承 Car)以及對應保時捷工廠類后后,仍只需要通過以上方法調用即可

// 先去保時捷工廠拿到一台保時捷
CarFactory baoShiJieFactory = new BaoShiJieFactory();
// 4S店拿到一台保時捷,給了你
Car baoShiJie = baoShiJieFactory.createCar();
baoShiJie.run();

(2) 定義

工廠方法模式:定義一個用於創建對象的接口,讓子類決定實例化哪一個類,工廠方法使一個類的實例化延遲到其子類

看其結構圖

(3) 優缺點

優點:

  • 對象的創建,被明確到了各個子工廠類中,不再需要在客戶端中考慮

  • 新內容增加非常方便,只需要增加一個想生成的類和創建其的工廠類

  • 不違背 “開閉原則”,后期維護,擴展方便

缺點:

  • 代碼量顯著增加

(三) 抽象工廠模式

抽象工廠模式是一種比較復雜的工廠模式,下面先直接通過代碼了解一下

還是說車,我們將車分為兩種,一種是普通轎車,一種是卡車,前面的工廠方法模式中,如果不斷的增加車的類型,這勢必會造成工廠過多,但是對於常見的車來說,還可以尋找可抽取的特點,來進行抽象

所以在此基礎之上,我們又分別設定了自動擋和手動擋兩種類型,所以兩兩搭配,就有四種情況了(eg:自動擋卡車,手動擋轎車等等)

(1) 創建抽象產品

  • 首先分別創建普通轎車和卡車的抽象類,然后定義兩個方法(這里我就寫成一樣的了,可以根據轎車和卡車的特點寫不同的方法)
public abstract class CommonCar {
    // 所有車都能,停車
    abstract void parking();
    // 所有車都能,換擋
    abstract void shiftGear();
}
public abstract class Truck  {
    // 所有車都能,停車
    abstract void parking();
    // 所有車都能,換擋
    abstract void shiftGear();
}

(2) 實現抽象產品

說明: A是自動的意思,H是手動的意思,eg:CommonCarA 代表普通自動擋轎車

  • 實現抽象產品——小轎車(自動擋)
public class CommonCarA extends CommonCar{
    @Override
    void parking() {
        System.out.println("自動擋轎車A,停車掛P檔");
    }

    @Override
    void shiftGear() {
        System.out.println("自動擋轎車A,可換擋 P N D R");
    }
}
  • 實現抽象產品——小轎車(手動擋)
public class CommonCarH extends CommonCar {
    @Override
    void parking() {
        System.out.println("手動擋轎車H,停車掛空擋,拉手剎");
    }

    @Override
    void shiftGear() {
        System.out.println("手動擋轎車H,可換擋 空 1 2 3 4 5 R");
    }
}
  • 實現抽象產品——貨車(自動擋)
public class TruckA extends Truck {
    @Override
    void parking() {
        System.out.println("自動擋貨車A,停車掛P檔");
    }

    @Override
    void shiftGear() {
        System.out.println("自動擋貨車A,可換擋 P N D R");
    }
}
  • 實現抽象產品——貨車(手動擋)
public class TruckH extends Truck {

    @Override
    void parking() {
        System.out.println("手動檔貨車H,停車掛空擋,拉手剎");
    }

    @Override
    void shiftGear() {
        System.out.println("手動檔貨車H,可換擋 空 1 2 3 4 5 R");
    }
}

(3) 創建抽象工廠

public interface CarFactory {
    // 創建普通轎車
    CommonCar createCommonCar();
    // 創建貨車
    Truck createTruckCar();
}

(4) 實現抽象工廠

通過自動擋手動擋這兩個抽象概念,創建出這兩個工廠,創建具有特定實現類的產品對象

  • 自動擋汽車工廠類
public class AutomaticCarFactory implements CarFactory {
    @Override
    public CommonCarA createCommonCar() {
        return new CommonCarA();
    }

    @Override
    public TruckA createTruckCar() {
        return new TruckA();
    }
}
  • 手動擋汽車工廠類
public class HandShiftCarFactory implements CarFactory {
    @Override
    public CommonCarH createCommonCar() {
        return new CommonCarH();
    }

    @Override
    public TruckH createTruckCar() {
        return new TruckH();
    }
}

(5) 測試一下

public class Test {
    public static void main(String[] args) {
        // 自動擋車工廠類
        CarFactory automaticCarFactory = new AutomaticCarFactory();
        // 手動擋車工廠類
        CarFactory handShiftCarFactory = new HandShiftCarFactory();

        System.out.println("=======自動擋轎車系列=======");
        CommonCar commonCarA = automaticCarFactory.createCommonCar();
        commonCarA.parking();
        commonCarA.shiftGear();

        System.out.println("=======自動擋貨車系列=======");
        Truck truckA = automaticCarFactory.createTruckCar();
        truckA.parking();
        truckA.shiftGear();

        System.out.println("=======手動擋轎車系列=======");
        CommonCar commonCarH = handShiftCarFactory.createCommonCar();
        commonCarH.parking();
        commonCarH.shiftGear();

        System.out.println("=======手動擋貨車系列=======");
        Truck truckH = handShiftCarFactory.createTruckCar();
        truckH.parking();
        truckH.shiftGear();
    }
}

運行結果:

=======自動擋轎車系列=======
自動擋轎車A,停車掛P檔
自動擋轎車A,可換擋 P N D R
=======自動擋貨車系列=======
自動擋貨車A,停車掛P檔
自動擋貨車A,可換擋 P N D R
=======手動擋轎車系列=======
手動擋轎車H,停車掛空擋,拉手剎
手動擋轎車H,可換擋 空 1 2 3 4 5 R
=======手動擋貨車系列=======
手動檔貨車H,停車掛空擋,拉手剎
手動檔貨車H,可換擋 空 1 2 3 4 5 R

補充兩個概念

  • 產品等級結構產品的等級結構就是其繼承結構,例如上述代碼中,CommonCar(普通轎車) 是一個抽象類,其子類有 CommonCarA (自動擋轎車)和 CommonCarH(手動擋轎車),則 普通轎車抽象類就與具體自動擋或者手動擋的轎車構成一個產品等級結構。
  • 產品族產品族是同一個工廠生產,位於不同產品等級結構中的一組產品,例如上述代碼中,CommonCarA(自動擋轎車)和 TruckA(自動擋貨車),都是AutomaticCarFactory(自動擋汽車工廠)這個工廠生成的

(6) 結構圖

看着結構圖,我們再捋一下

首先 AbstractProductA 和 AbstractProductB 是兩個抽象產品,分別對應我們上述代碼中的 CommonCar 和 Truck,為什么是抽象的,因為它們可以都有兩種不同的實現,即自動擋轎車和自動貨車,手動擋轎車和手動擋卡車

ProductA1 和 ProductA2 和 ProductB1 和 ProductB2 就是具體的實現,代表 CommonCarA 和 CommonCarH 和 TruckA 和 TruckH

抽象工廠 AbstractFactory 里包含了所有產品創建的抽象方法,ConcreteFactory1 和 ConcreteFactory2 就是具體的工廠,通常是在運行時再創建一個 ConcreteFactory 的實例,這個工廠再創建具有特定實現的產品對象,也就是說為了創建不同的產品對象,客戶端應該使用不同的具體工廠

(7) 反射+配置文件實現優化

抽象工廠說白了就是通過內容抽象的方式,減少了工廠的數量,同時在具體工廠我們可以這么用

CarFactory factory = new AutomaticCarFactory();

具體工廠只需要在初始化的時候出現一次,這也使得修改一個具體工廠也是比較容易的

但是缺點也是非常明顯,當我想擴展一,比如加一個拖拉機類型,我就需要修改 CarFactory接口,AutomaticCarFactory 類 HandShiftCarFactory 類,(當然,拖拉機貌似沒有什么自動擋,我只是為了舉例子),還需要增加拖拉機對應的內容

也就是說,增加的基礎上,我還需要修改原先的三個類,這是一個非常顯著的缺點

除此之外還有一個問題,如果很多地方都聲明了

CarFactory factory = new AutomaticCarFactory();

並且進行了調用,如果我更換了這個工廠,就需要大量的進行修改,很顯然這一點是有問題的,我們下面來使用反射優化一下

public class Test {
    public static void main(String[] args) throws Exception {

        Properties properties = new Properties();
        // 使用ClassLoader加載properties配置文件生成對應的輸入流
        InputStream in = Test.class.getClassLoader().getResourceAsStream("config.properties");
        // 使用properties對象加載輸入流
        properties.load(in);
        //獲取key對應的value值
        String factory = properties.getProperty("factory");

        CarFactory automaticCarFactory = (CarFactory) Class.forName(factory).newInstance();

        System.out.println("======轎車系列=======");
        CommonCar commonCarA = automaticCarFactory.createCommonCar();
        commonCarA.parking();
        commonCarA.shiftGear();

        System.out.println("=======貨車系列=======");
        Truck truckA = automaticCarFactory.createTruckCar();
        truckA.parking();
        truckA.shiftGear();

    }
}

config.properties

factory=cn.ideal.factory.abstractFactory.AutomaticCarFactory
#factory=cn.ideal.factory.abstractFactory.HandShiftCarFactory

運行結果:

=======轎車系列=======
自動擋轎車A,停車掛P檔
自動擋轎車A,可換擋 P N D R
=======貨車系列=======
自動擋貨車A,停車掛P檔
自動擋貨車A,可換擋 P N D R

通過反射+配置文件我們就可以使得使用配置文件中的鍵值對(字符串)來實例化對象,而變量是可以更換的,也就是說程序由編譯時轉為運行時,增大了靈活性,去除了判斷的麻煩

回到前面的問題,如果我們現在要增加一個新的內容,內容的增加沒什么好說的,這是必須的,這是擴展,但是對於修改我們卻要盡量關閉,現在我們可以通過修改配置文件來達到實例化不同具體工廠的方式

但是還需要修改三個類,以添加新內容,這里還可以通過簡單工廠來進行優化,也就是去掉這幾個工廠,使用一個簡單工廠,其中寫入createCommonCar(); 等這些方法, 再配合反射+配置文件也能實現剛才的效果,這樣如果新增內容的時候只需要修改配置文件后,再修改這一個類就可以,即增加一個 createXXX 方法,就不需要修改多個內容了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM