跟我學設計模式之工廠模式

工廠模式應用很是之廣,在JDK底層源碼以及各大主流框架中隨處可見,通常以Factory結尾命名的類,好比Mybatis中的SqlSessionFactorySpring中的BeanFactory等,都是工廠模式的典型表明。java

1、簡單工廠模式

1.1 概念

簡單工廠模式又稱爲靜態工廠模式,屬於設計模式中的建立型模式。簡單工廠模式經過對外提供一個靜態方法來統一爲類建立實例,目的是實現類與類之間解耦:客戶端不須要知道這個對象是如何被穿建立出來的,只須要調用簡單工廠模式的方法來統一建立就能夠了,從而明確了各個類的職責。git

1.2 示例

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

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)。所以,若是須要增長新的類型,就不得不去修改原來的代碼,違反開閉原則。ide

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

2、工廠方法模式

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

那就是它把全部對象的建立都集中在同一個工廠類裏面了,所以,當新增一個新對象時,必然會須要修改這個共享工廠類,違反開閉原則天然不可避免。post

  • 解決方案

既然問題關鍵在於,全部對象的建立都跟這個惟一的工廠類耦合了,那我每一個對象各自都配置一個單獨的工廠類,這個工廠類只建立各自類型的對象,那這樣不就解決耦合的問題了嗎?性能

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、抽象工廠模式

抽象工廠模式出現,就是爲了解決上述工廠方法模式存在的問題,能夠當作是工廠方法模式的升級。

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、總結

4.1 如何選擇

工廠模式的三種形式都介紹完了,那咱們實際開發中該如何去選擇呢?
  1. 從設計原則來講,簡單工廠模式不符合開閉原則。可是很神奇,在實際場景中,簡單工廠模式確實用的最多的。
  2. 工廠方法模式是專門用於解決單個對象建立工做,自己模式沒問題,也符合開閉原則。可是存在工廠類數量膨脹的問題。若是須要建立的工廠類不是不少,是一種不錯的選擇。
  3. 抽象工廠模式天生就是爲生產產品族而生的。因此若是你須要建立的對象很是之多,可是對象之間存在明顯產品族特徵,那麼這個時候用抽象工廠模式很是合適。

4.2 示例源碼

Github 示例代碼

4.3 技術交流

  1. 風塵博客:https://www.dustyblog.cn
  2. 風塵博客-掘金
  3. 風塵博客-博客園
  4. Github
  5. 公衆號

風塵博客
參考文章

相關文章
相關標籤/搜索