工廠模式-將對象的建立封裝起來

公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml

工廠模式(Factory Design Pattern)可細分爲三種,分別是簡單工廠工廠方法抽象工廠,它們都是爲了更好的建立對象。java

所謂的「工廠」,就是用來將建立對象的代碼封裝起來,由於這部分代碼未來變更的概率很大,因此這裏的「工廠」的實質做用就是「封裝變化」,以便於維護。git

其中用到了「針對接口編程,而非針對實現編程」的設計原則。github

下面經過一個銷售飲料的例子,來對這三種工廠模式進行介紹。shell

1,簡單工廠模式

假如如今有一位老闆,想開一家飲料店,該店銷售各類口味的飲料,好比蘋果味,香蕉味,橙子味等。編程

將這些飲料用代碼來表示,以下:設計模式

class Drink {
    public void packing() {
        //
    }
}

class DrinkApple extends Drink {
}

class DrinkBanana extends Drink {
}

class DrinkOrange extends Drink {
}

Drink 類爲全部其它味道的飲料的父類。app

當有顧客來購買飲料的時候,顧客須要說明想買哪一種口味的飲料,而後服務員就去將該口味的飲料取過來,包裝好,而後交給顧客。框架

下面咱們用最簡單直接的代碼,來模擬飲料店和賣飲料的過程,以下:ide

class DrinkStore {

    public Drink sellDrink(String flavor) {
        Drink drink;

        if (flavor.equals("apple")) {
            drink = new DrinkApple();
        } else if (flavor.equals("banana")) {
            drink = new DrinkBanana();
        } else if (flavor.equals("orange")) {
            drink = new DrinkOrange();
        } else {
            drink = new Drink();
        }

        drink.packing();

        return drink;
    }
}

可是這種實現方式有個問題,就是當須要下架舊飲料或上架新飲料的時候,會致使下面這部分代碼被頻繁的修改:

if (flavor.equals("apple")) {
    drink = new DrinkApple();
} else if (flavor.equals("banana")) {
    drink = new DrinkBanana();
} else if (flavor.equals("orange")) {
    drink = new DrinkOrange();
} else {
    drink = new Drink();
}

那這就違背了設計原則中的開閉原則代碼應該對擴展開發,對修改關閉。因此咱們須要對該代碼進行改進,那如何修改呢?

簡單工廠模式告訴咱們要將相似這樣的代碼封裝到一個裏邊,這個類就叫作簡單工廠類,該類中提供一個方法,它能夠生產咱們所須要的各類對象。

下面用代碼來模擬這個簡單工廠類,以下:

class SimpleDrinkFactory {
    public Drink createDrink(String flavor) {
        Drink drink;

        // 這段容易被頻繁修改的代碼,被封裝到了工廠類中
        if (flavor.equals("apple")) {
            drink = new DrinkApple();
        } else if (flavor.equals("banana")) {
            drink = new DrinkBanana();
        } else if (flavor.equals("orange")) {
            drink = new DrinkOrange();
        } else {
            drink = new Drink();
        }

        return drink;
    }
}

能夠看到,createDrink 方法完成了建立 Drink 的任務。

createDrink 方法也能夠定義成靜態方法,優勢是在使用 createDrink 方法時不須要再建立對象,缺點是不能再經過繼承的方式來改變 createDrink 方法的行爲。

下面來看如何使用 SimpleDrinkFactory 類:

class DrinkStore {
    private SimpleDrinkFactory factory;

    public DrinkStore(SimpleDrinkFactory factory) {
        this.factory = factory;
    }

    public Drink sellDrink(String flavor) {
        Drink drink = factory.createDrink(flavor);

        drink.packing();

        return drink;
    }
}

能夠看到,咱們將 SimpleDrinkFactory 類的對象做爲 DrinkStore 類的一個屬性,通過改進後的 sellDrink 方法就不須要再被頻繁修改了。若是再須要上架下架飲料,則去修改簡單工廠類 SimpleDrinkFactory 便可。

我將完整的簡單工廠代碼放在了這裏,供你們參考,類圖以下:

在這裏插入圖片描述

2,工廠方法模式

簡單工廠模式從嚴格意義來講並非一個設計模式,而更像一種編程習慣。

工廠方法模式定義了一個建立對象的接口(該接口是一個抽象方法,也叫作「工廠方法」),但由子類(實現抽象方法)決定要實例化的類是哪一個。

在工廠方法模式中,父類並不關心子類的具體實現,可是父類給了子類一個「規範」,讓子類必須「生成」父類想要的東西。

工廠方法將類的實例化推遲到了子類中,讓子類來控制實例化的細節,也就是將建立對象的過程封裝了起來。而真正使用實例的是父類,這樣就將實例的「實現」從「使用」中解耦出來。所以,工廠方法模式比簡單工廠模式更加有「彈性」。

下面咱們來看下如何用工廠方法模式來改進上面的代碼。

首先,定義 DrinkStoreAbstract ,它是一個抽象父類:

abstract class DrinkStoreAbstract {

    // final 防止子類覆蓋
    public final Drink sellDrink(String flavor) {

        Drink drink = factoryMethod(flavor);    // 使用實例

        drink.packing();

        return drink;
    }

    // 子類必須實現
    protected abstract Drink factoryMethod(String flavor);
}

上面代碼中 factoryMethod 方法就是所謂的工廠方法,它是一個抽象方法,子類必須實現該方法。

factoryMethod 方法負責生成對象,使用對象的是父類,而實際生成對象的則是子類。

接下來定義一個具體的 DrinkStore 類,它是 DrinkStoreAbstract 的子類:

class DrinkStore extends DrinkStoreAbstract {

    @Override
    public Drink factoryMethod(String flavor) {
        Drink drink;

        if (flavor.equals("apple")) {
            drink = new DrinkApple();
        } else if (flavor.equals("banana")) {
            drink = new DrinkBanana();
        } else if (flavor.equals("orange")) {
            drink = new DrinkOrange();
        } else {
            drink = new Drink();
        }

        return drink;
    }
}

能夠看到,子類中的 factoryMethod 方法有了具體的實現。

若是須要上架下架飲料,則去修改子類中的工廠方法 factoryMethod 便可。而 DrinkStoreAbstract 做爲一個「框架」,無須改動。

完整的工廠方法代碼放在了這裏,供你們參考,類圖以下:

在這裏插入圖片描述

圖中的粉色區域是工廠方法模式的重點關注區。

3,依賴倒置原則

在介紹抽象工廠模式以前,咱們先來看看什麼是依賴倒置原則

依賴倒置包含了依賴倒置兩個詞。

咱們先來看看「依賴」,「依賴」是指類與類之間的依賴關係。

在本文剛開始時的 sellDrink 方法是這麼寫的:

public Drink sellDrink(String flavor) {
      Drink drink;

      if (flavor.equals("apple")) {
          drink = new DrinkApple();
      } else if (flavor.equals("banana")) {
          drink = new DrinkBanana();
      } else if (flavor.equals("orange")) {
          drink = new DrinkOrange();
      } else {
          drink = new Drink();
      }

      drink.packing();

      return drink;
  }

sellDrink 方法依賴了三個具體類,若是飲料的味道繼續增長的話,那麼 sellDrink 方法將依賴更多的具體類

這會致使只要任意一個具體類發生改變,sellDrink 方法就不得不去改變,也就是類 A 須要改變(A 也是一個具體類)。

在這裏插入圖片描述

在上面這個關係圖中,A 稱爲高層組件,各個具體類稱爲低層組件,因此在這幅圖中,高層組件依賴了低層組件。

依賴倒置原則是指「要依賴抽象,而不依賴具體類」。更具體來講就是,高層組件不該該依賴低層組件,高層組件和低層組件都應該依賴抽象類

那麼怎樣才能達到依賴倒置原則呢?工廠方法就能夠!

在通過工廠方法模式的改造以後,最終的 DrinkStoreAbstract 類中的 sellDrink 方法變成了下面這樣:

public final Drink sellDrink(String flavor) {

      Drink drink = factoryMethod(flavor);    // 使用實例

      drink.packing();

      return drink;
  }

這使得 sellDrink 的所在類再也不依賴於具體類,而依賴於一個抽象方法 factoryMethod,而 factoryMethod 方法依賴於 Drink,而後,各個具體類也依賴於 DrinkDrink 是一個廣義上的「抽象接口」。

這樣,高層組件和低層組件都依賴於 Drink 抽象,關係圖變成了下面這樣:

在這裏插入圖片描述

以前各個具體類的箭頭是向下指的,而如今各個具體類的箭頭是向上指的,箭頭的方向倒了過來,這就是所謂的依賴倒置

依賴倒置原則使得高層組件和低層組件都依賴於同一個抽象。

那怎樣才能避免違反依賴倒置原則呢?有下面三個指導方針:

  • 變量不要持有具體類的引用。
    • 須要 new 對象的地方,要改成工廠方法。
  • 類不要派生自具體類,而要從抽象類或接口派生。
    • 派生自具體類,就會依賴具體類。
  • 子類不要覆蓋父類中已實現的方法。
    • 父類中已實現的方法應該被全部子類共享,而不是覆蓋。

固然事情沒有絕對的,上面三個指導方針,是應該儘可能作到,而不是必須作到

4,抽象工廠模式

下面來介紹抽象工廠模式

假如臨近春節,飲料店老闆爲了更好的銷售飲料,準備購買一批禮盒,來包裝飲料。爲了保證禮盒的質量,規定禮盒只能從特定的地方批發,好比北京,上海等。

那咱們就定義一個接口,讓全部的禮盒都派生自這個接口,以下:

interface DrinkBoxFactory {
    String createBox();
}

class BeiJingBoxFactory implements DrinkBoxFactory {

    @Override
    public String createBox() {
        return "BeijingBox";
    }
}

class ShangHaiBoxFactory implements DrinkBoxFactory {

    @Override
    public String createBox() {
        return "ShangHaiBox";
    }
}

下面須要編寫 Drink 類,咱們讓 Drink 類是一個抽象類,以下:

abstract class Drink {
    String flavor;
    protected abstract void packing();
}

須要注意的是,在抽象類 Drink 中有一個抽象方法 packingDrinkpacking 的具體實現交給每一個派生類。Drink 類只規定派生類中須要實現一個 packing,但並不關心它的具體實現。

下面編寫每種口味的飲料,以下:

class DrinkApple extends Drink {
    DrinkBoxFactory boxFactory;

    public DrinkApple(DrinkBoxFactory boxFactory) {
        this.boxFactory = boxFactory;
        this.flavor = "DrinkApple";
    }

    @Override
    public void packing() {
        System.out.println(flavor + boxFactory.createBox());
    }
}

class DrinkBanana extends Drink {
    DrinkBoxFactory boxFactory;

    public DrinkBanana(DrinkBoxFactory boxFactory) {
        this.boxFactory = boxFactory;
        this.flavor = "DrinkBanana";
    }

    @Override
    public void packing() {
        System.out.println(flavor + boxFactory.createBox());
    }
}

class DrinkOrange extends Drink {
    DrinkBoxFactory boxFactory;

    public DrinkOrange(DrinkBoxFactory boxFactory) {
        this.boxFactory = boxFactory;
        this.flavor = "DrinkOrange";
    }

    @Override
    public void packing() {
        System.out.println(flavor + boxFactory.createBox());
    }
}

能夠看到每種口味的飲料中都有一個 boxFactory 對象,在 packing 時,從 boxFactory 中獲取禮盒。

注意 boxFactory 是一個接口類對象,而不是一個具體類對象,所以,boxFactory 的具體對象是由客戶決定的。

而後,DrinkStoreAbstract 仍是沿用工廠方法模式中的定義,以下:

abstract class DrinkStoreAbstract {

    // final 防止子類覆蓋
    public final Drink sellDrink(String flavor) {

        Drink drink = factoryMethod(flavor);    // 使用實例

        drink.packing();

        return drink;
    }

    // 子類必須實現
    protected abstract Drink factoryMethod(String flavor);
}

而後咱們實現使用北京禮盒包裝的Store 和使用上海禮盒包裝的Store,以下:

class BeijingDrinkStore extends DrinkStoreAbstract {
    
    @Override
    protected Drink factoryMethod(String flavor) {
        Drink drink = null;
        DrinkBoxFactory factory = new BeiJingBoxFactory();

        if (flavor.equals("apple")) {
            drink = new DrinkApple(factory);
        } else if (flavor.equals("banana")) {
            drink = new DrinkBanana(factory);
        } else if (flavor.equals("orange")) {
            drink = new DrinkOrange(factory);
        } 
                
        return drink;
    }
}

class ShangHaiDrinkStore extends DrinkStoreAbstract {

    @Override
    protected Drink factoryMethod(String flavor) {
        Drink drink = null;
        DrinkBoxFactory factory = new ShangHaiBoxFactory();

        if (flavor.equals("apple")) {
            drink = new DrinkApple(factory);
        } else if (flavor.equals("banana")) {
            drink = new DrinkBanana(factory);
        } else if (flavor.equals("orange")) {
            drink = new DrinkOrange(factory);
        }

        return drink;
    }
}

通過這麼一些列的改動,咱們到底作了些什麼呢?事情的原由是老闆須要一些禮盒來包裝飲料,這就是需求增長了。

所以咱們引入了一個抽象工廠,即 DrinkBoxFactory ,這個接口是全部禮盒工廠的父類,它給了全部子類一個「規範」。

抽象工廠模式提供了一個接口,用於建立相關對象的家族。該模式旨在爲客戶提供一個抽象接口(本例中就是 DrinkBoxFactory 接口),從而去建立一些列相關的對象,而不需關心實際生產的具體產品是什麼,這樣作的好處是讓客戶從具體的產品中解耦

最終,客戶是這樣使用 DrinkStoreAbstract 的,以下:

public class AbstractMethod {
    public static void main(String[] args) {
        DrinkStoreAbstract beiStore = new BeijingDrinkStore();
        beiStore.sellDrink("apple");
        beiStore.sellDrink("banana");
        beiStore.sellDrink("orange");

        DrinkStoreAbstract shangStore = new ShangHaiDrinkStore();
        shangStore.sellDrink("apple");
        shangStore.sellDrink("banana");
        shangStore.sellDrink("orange");
    }
}

完整的抽象工廠模式代碼放在了這裏,供你們參考,類圖以下:

在這裏插入圖片描述

從該圖能夠看出,抽象工廠模式比工廠方法模式更復雜了一些,另外仔細觀察它們兩個的類圖,各自所關注的地方(粉紅色區域)也是不同的

工廠方法與抽象工廠的相同點都是將對象的建立封裝起來。不一樣點是:

  • 工廠方法主要關注將類的實例化推遲到子類中
  • 抽象工廠主要關注建立一系列相關的產品家族

5,總結

工廠模式使用到的設計原則有:

  • 針對接口編程,而非針對實現編程。
  • 開閉原則。
  • 依賴倒置原則。
  • 封裝變化。

本篇文章介紹了三種工廠模式,工廠模式在實際應用中很是普遍,好比 Java 工具類中的 CalendarDateFormat 等。

(本節完。)


推薦閱讀:

單例模式-讓一個類只有一個實例

設計模式之高質量代碼


歡迎關注做者公衆號,獲取更多技術乾貨。

碼農充電站pro

相關文章
相關標籤/搜索