1. 程式人生 > >從BWM生產學習工廠模式

從BWM生產學習工廠模式

工廠模式應用非常之廣,在JDK底層原始碼以及各大主流框架中隨處可見,一般以Factory結尾命名的類,比如Mybatis中的SqlSessionFactorySpring中的BeanFactory等,都是工廠模式的典型代表。

一、簡單工廠模式

1.1 概念

簡單工廠模式又稱為靜態工廠模式,屬於設計模式中的建立型模式。簡單工廠模式通過對外提供一個靜態方法來統一為類建立例項,目的是實現類與類之間解耦:客戶端不需要知道這個物件是如何被穿創建出來的,只需要呼叫簡單工廠模式的方法來統一建立就可以了,從而明確了各個類的職責。

1.2 示例

簡單工廠模式,以生產汽車輪胎為例。

1.2.1 實體類

  • 輪胎通用屬性
public class Tire {
    /**
     * 通用屬性
     */
    private String common;
}
  • 賓士車輪胎

包含通用屬性外還有自己的特有屬性

public class TireForBenz extends Tire{


    Tire tire;
    /**
     * 特有屬性
     */
    private String benz;

    public TireForBenz() {
        this.benz = "得到 Benz 輪胎";
    }


    @Override
    public String toString() {
        return "["+this.benz +"]";
    }
}
  • 寶馬車輪胎

包含通用屬性外還有自己的特有屬性

public class TireForBwm extends Tire{

    Tire tire;

    /**
     * 特有屬性
     */
    private String bwm;

    public TireForBwm() {
        this.bwm = "得到 Bwm 輪胎";
    }

    @Override
    public String toString() {
        return "["+this.bwm +"]";
    }
}

1.2.2 生產工藝

  • 生產輪胎的抽象方法,各個產線有自己的方式生產
public interface TireFactory {

    Tire produceTire();
}
  • 賓士汽車輪胎產線

重寫生產輪胎的方法返回賓士型輪胎。

public class BenzTireFactory implements TireFactory {

    /**
     * 生產賓士輪胎
     */
    @Override
    public Tire produceTire() {
        System.out.println("賓士輪胎生產中。。。");
        return new TireForBenz();
    }
}
  • 寶馬汽車輪胎產線

重寫生產輪胎的方法返回寶馬型輪胎。

public class BwmTireFactory implements TireFactory {

    /**
     * 生產寶馬輪胎
     */
    @Override
    public TireForBwm produceTire() {
        System.out.println("寶馬輪胎生產中。。。");
        return new TireForBwm();
    }
}

1.2.3 輪胎工廠類

通過傳入的品牌名稱呼叫相應產線生產相應品牌的輪胎

public class SimpleFactoryMode {

    public static TireFactory produceCar(String name) {
        if ("BenzTireFactory".equals(name)) {
            return new BenzTireFactory();
        }
        if ("BwmTireFactory".equals(name)) {
            return new BwmTireFactory();
        }
        return null;
    }
}

1.2.4 測試

客戶端通過工廠類獲取例項物件。

  • 測試方法
 @Test
public void simpleFactoryModeTest() {
    // 造賓士輪胎
    TireFactory benz = SimpleFactoryMode.produceCar("BenzTireFactory");
    if (null != benz) {
        benz.produceTire();
    }else {
        System.out.println("工廠暫時無法生產賓士輪胎");
    }
    // 造寶馬輪胎
    TireFactory bwm = SimpleFactoryMode.produceCar("BwmTireFactory");
    if (null != bwm) {
        bwm.produceTire();
    }else {
        System.out.println("工廠暫時無法生產寶馬輪胎");
    }
    // 造本田汽輪胎(工廠無該方法)
    TireFactory honda = SimpleFactoryMode.produceCar("Honda");
    if (null != honda) {
        honda.produceTire();
    }else {
        System.out.println("工廠暫時無法生產本田輪胎");
    }
}
  • 結果
賓士輪胎生產中。。。
寶馬輪胎生產中。。。
工廠暫時無法生產本田輪胎

該方式確實能完成不同品牌的輪胎生產,但是,有個問題:方法引數是字串,可控性有待提升。

1.3 簡單工廠模式優化

不要通過傳入的字串來判斷需要建立物件,而是客戶端想要建立什麼物件,只需要傳入具體的實現類就可以了,然後通過Java的反射來建立物件。

public static TireFactory produceCar(Class<? extends TireFactory> clazz) {
    try {
        // 通過Java的反射來建立物件
        return clazz.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

每次建立物件都是通過反射來建立的,所以在效能上是有一定的損耗。

  • 測試
public void simpleFactoryModeUpgradeTest() {
    // 造賓士輪胎
    TireFactory benzTire = SimpleFactoryMode.produceCar(BenzTireFactory.class);
    TireForBenz benz = (TireForBenz) benzTire.produceTire();
    System.out.println(benz.toString());
    // 造寶馬輪胎
    TireFactory bwmTire = SimpleFactoryMode.produceCar(BwmTireFactory.class);
    TireForBwm bwm = (TireForBwm) bwmTire.produceTire();
    System.out.println(bwm.toString());
}
  • 結果
賓士輪胎生產中。。。
[得到 Benz 輪胎]
寶馬輪胎生產中。。。
[得到 Bwm 輪胎]

1.4 小結

簡單工廠模式確實在一定程度上實現程式碼的解耦,而這種解耦的特點在於,這種模式將物件的建立和使用分離。這種模式的本質在於通過一個傳入的引數,做if...else判斷,來達到返回不同型別物件的目的。缺點也很明顯,不符合開閉原則(比如新增一個保時捷輪胎的生產,除了需要增加實體和生產方法,還需要修改工廠類SimpleFactoryMode.java)。因此,如果需要增加新的型別,就不得不去修改原來的程式碼,違反開閉原則。

  • 簡單工廠模式優點:
  1. 簡單優化了軟體體系結構,明確了各自功能模組的職責和權利;
  2. 通過工廠類,外界不需要直接建立具體產品物件,只需要負責消費,不需要關心內部如何建立物件。
  • 簡單工廠模式缺點:
  1. 改進前的簡單工廠模式全部建立邏輯都集中在一個工廠類中,能建立的類只能是考慮到的,如果需要新增新的類,就必須改變工廠類了;
  2. 改進前的簡單工廠模式隨著具體產品的不斷增多,可能會出現共產類根據不同條件建立不同例項的需求,這種對條件的判斷和對具體產品型別的判斷交錯在一起,很難避免功能模組的蔓延,對系統的維護和擴充套件不利;
  3. 改進後的簡單工廠模式主要是使用反射效率會低一些。

二、工廠方法模式

  • 簡單工廠模式之所以違反開閉原則,關鍵在於什麼?

那就是它把所有物件的建立都集中在同一個工廠類裡面了,因此,當新增一個新物件時,必然會需要修改這個共享工廠類,違反開閉原則自然不可避免。

  • 解決方案

既然問題關鍵在於,所有物件的建立都跟這個唯一的工廠類耦合了,那我每個物件各自都配置一個單獨的工廠類,這個工廠類只建立各自型別的物件,那這樣不就解決耦合的問題了嗎?

2.1 概念

工廠方法模式是指定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類。工廠方法讓類的例項化推遲到子類中進行。在工廠方法模式中使用者只需要關心所需產品對應的工廠,無須關心建立細節,而且加入新的產品符合開閉原則。

2.2 示例

工廠方法模式,以生產發動機為例。

2.2.1 實體

  • 發動機的通用屬性
public class Engine {

    /**
     * 型號
     */
    private String common;

}
  • 賓士發動機

包含通用屬性外還有自己的特有屬性

public class EngineForBenz extends Engine{

    Engine engine;
    /**
     * 特有屬性
     */
    private String benz;

    public EngineForBenz() {
        this.benz = "得到 Benz 發動機";
    }

    @Override
    public String toString() {
        return "["+this.benz +"]";
    }
}
  • 寶馬發動機

包含通用屬性外還有自己的特有屬性

public class EngineForBwm extends Engine{

    Engine engine;
    /**
     * 特有屬性
     */
    private String bwm;

    public EngineForBwm() {
        this.bwm = "得到 Bwm 發動機";
    }

    @Override
    public String toString() {
        return "["+this.bwm +"]";
    }
}

2.2.2 生產工藝(發動機的工廠類)

  • 抽象工廠類,定義生產發動機的方法,各個產線自己的去實現
public interface EngineFactory<T> {

    Engine produceEngine();

}
  • 建立賓士子工廠,實現其的工藝
public class BenzEngineFactory implements EngineFactory<EngineForBenz> {

    /**
     * 生產賓士發動機
     */
    @Override
    public Engine produceEngine() {
        System.out.println("賓士發動機生產中。。。");
        return new EngineForBenz();
    }
}
  • 建立寶馬子工廠,實現其的工藝
public class BwmEngineFactory implements EngineFactory<EngineForBwm> {

    /**
     * 生產寶馬發動機
     */
    @Override
    public Engine produceEngine() {
        System.out.println("寶馬發動機生產中。。。");
        return new EngineForBwm();
    }
}

2.2.3 測試

@Test
public void factoryModeTest() {
    // 造賓士發動機
    EngineFactory car = new BenzEngineFactory();
    EngineForBenz benz = (EngineForBenz) car.produceEngine();
    System.out.println(benz.toString());
    // 造寶馬發動機
    EngineFactory carFactory = new BwmEngineFactory();
    EngineForBwm bwm = (EngineForBwm) carFactory.produceEngine();
    System.out.println(bwm.toString());
}
  • 結果
賓士發動機生產中。。。
[得到 Benz 發動機]
寶馬發動機生產中。。。
[得到 Bwm 發動機]

2.3 小結

工廠方法模式輕鬆解決了簡單工廠模式的問題,符合開閉原則。在上面例子中,當需要新增一個保時捷汽車,此時只需要提供一個對應的EngineForBSJ.java實現produceEngine()方法即可,對於原先程式碼再不需要做任何修改。

但是每個型別的物件都會有一個與之對應的工廠類。如果物件的型別非常多,意味著會需要建立很多的工廠實現類,造成類數量膨脹,對後續維護帶來一些麻煩。

  • 缺點
  1. 客戶端(應用層)不依賴於產品類例項如何被建立、實現等細節;
  2. 一個類通過其子類來指定建立哪個物件。
  • 缺點
  1. 類的個數容易過多,增加複雜度;
  2. 增加了系統的抽象性和理解難度。

三、抽象工廠模式

抽象工廠模式出現,就是為了解決上述工廠方法模式存在的問題,可以看成是工廠方法模式的升級。

3.1 背景

  • 類數量膨脹的情景

工廠方法模式建立的物件其實歸根到底都是同一類物件。以汽車生產為例,無論是輪胎還是發動機,都是汽車生產的一部分,都是屬於汽車生產的過程。如下圖:

由上圖我們可以發現,雖然分為賓士車和寶馬車,但是從工廠方法角度,他們都屬於汽車這一類別,這就導致了需要單獨為每一個零件指定各自的工廠類,從而導致了類數量膨脹的問題。

  • 解決方案

既然這樣,我們可以把每類汽車指定一個工廠,然後再讓不同產線去生產他需要的產品,如下圖

這樣當每一類物品元件數量特別多,可以把它稱為產品族。抽象工廠模式就是為了建立一系列以產品族為單位的物件,這樣在需要建立大量系列物件時可以大大提高開發效率,降低維護成本。

3.2 示例

因為賓士輪胎/寶馬輪胎/賓士發動機/寶馬發動機的實體在前面已經建立過,這裡就直接用了。

3.2.1 汽車工廠類(頂層抽象工廠類)

該類已包含輪胎/發動機生產,具體實體鍵一/二中相關實體。

public interface CarFactory {

    /**
     * 準備生產
     */
    void init();

    /**
     * 生產輪胎
     * @return
     */
    Tire produceTire();

    /**
     * 生產發動機
     * @return
     */
    Engine produceEngine();
}

3.2.2 賓士汽車產品族(賓士汽車工廠類)

public class BenzCarFactory implements CarFactory{


    @Override
    public void init() {
        System.out.println("----------------------- 賓士汽車準備生產 -----------------------");
    }

    @Override
    public Tire produceTire() {
        System.out.println("正在生產賓士輪胎");
        return new TireForBenz();
    }

    @Override
    public Engine produceEngine() {
        System.out.println("正在生產賓士發動機");
        return new EngineForBenz();
    }
}

3.2.2 寶馬汽車產品族(寶馬汽車工廠類)

public class BwmCarFactory implements CarFactory{


    @Override
    public void init() {
        System.out.println("----------------------- 寶馬汽車準備生產 -----------------------");
    }

    @Override
    public Tire produceTire() {
        System.out.println("正在生產寶馬輪胎");
        return new TireForBwm();
    }

    @Override
    public Engine produceEngine() {
        System.out.println("正在生產寶馬發動機");
        return new EngineForBwm();
    }
}

3.2.3 測試

@Test
public void abstractFactoryModeTest() {
    // 生產賓士整車的零部件
    CarFactory benz = new BenzCarFactory();
    benz.init();
    TireForBenz benzTire = (TireForBenz) benz.produceTire();
    System.out.println(benzTire.toString());

    EngineForBenz benzEngine = (EngineForBenz) benz.produceEngine();
    System.out.println(benzEngine.toString());

    // 生成寶馬整車的零部件d
    CarFactory bwm = new BwmCarFactory();
    bwm.init();
    TireForBwm bwmTire = (TireForBwm) bwm.produceTire();
    System.out.println(bwmTire.toString());

    EngineForBwm bwmEngine = (EngineForBwm) bwm.produceEngine();
    System.out.println(bwmEngine.toString());
}
  • 結果
----------------------- 賓士汽車準備生產 -----------------------
正在生產賓士輪胎
[得到 Benz 輪胎]
正在生產賓士發動機
[得到 Benz 發動機]
----------------------- 寶馬汽車準備生產 -----------------------
正在生產寶馬輪胎
[得到 Bwm 輪胎]
正在生產寶馬發動機
[得到 Bwm 發動機]

3.3 思考

既然說抽象工廠模式是工廠方法模式的升級,那到底升級了啥?

其實是由原來的單一產品的生產升級成為了系列產品的生產。設想一下,假設上面汽車的例子中,每一品牌汽車中就只生產一種部件,比如就只生產發動機,不生產輪胎等其他元件了,如下圖

發現了什麼沒有?抽象工廠模式居然轉變為我們之前講過的工廠方法模式了!換句話說,當你的產品族中只生產一種產品的時候,你的抽象工廠模式其實已經退化為工廠方法模式了。反過來說,當生產多種產品時,工廠方法模式就進化為抽象工廠模式。

3.4 小結

抽象工廠模式在建立大量系列物件時可以大大提高開發效率,就是為生產產品族而生的,而對於生產單一產品卻無能為力。

如果需要新增一個新的產品族,那就簡單了,比如新增一個保時捷汽車,那就只需要新增一個保時捷汽車的工廠實現類就好了,並不會對原有的程式碼造成任何影響。

但是,如果假設在汽車中,我需要再加一個元件,比如倒車影像,怎麼操作?你需要在CarFactory介面中新增返回倒車影像物件的介面。這一加不得了了......所有品牌汽車實現類全部需要修改並追加該方法的實現,違反了開閉原則。

  • 抽象工廠模式優點:

建立大量系列物件時可以大大提高開發效率,降低維護成本。

  • 抽象工廠模式缺點:
  1. 規定了所有可能被建立的產品集合,產品族中擴充套件新的產品困難,需要修改抽象工廠的介面;
  2. 增加了系統的抽象性和理解難度。

四、總結

4.1 如何選擇

工廠模式的三種形式都介紹完了,那我們實際開發中該如何去選擇呢?

  1. 從設計原則來說,簡單工廠模式不符合開閉原則。但是很神奇,在實際場景中,簡單工廠模式確實用的最多的。
  2. 工廠方法模式是專門用於解決單個物件建立工作,本身模式沒問題,也符合開閉原則。但是存在工廠類數量膨脹的問題。如果需要建立的工廠類不是很多,是一種不錯的選擇。
  3. 抽象工廠模式天生就是為生產產品族而生的。所以如果你需要建立的物件非常之多,但是物件之間存在明顯產品族特徵,那麼這個時候用抽象工廠模式非常合適。

4.2 示例原始碼

Github 示例程式碼

4.3 技術交流

  1. 風塵部落格:https://www.dustyblog.cn
  2. 風塵部落格-掘金
  3. 風塵部落格-部落格園
  4. Github
  5. 公眾號


    參考文章