設計模式 --並不簡單的工廠模式

正號先生發表於2020-08-07

前言

上幾節課我們講了單例模式,今天我們再來講另外一個比較常用的建立型模式:工廠模式(Factory Design Pattern)。

一般情況下,工廠模式分為三種更加細分的型別:簡單工廠、工廠方法和抽象工廠。實際上,這三種我們最常用得是第一種簡單工廠和工廠方法模式。而抽象工廠的原理稍微複雜點,在實際的專案中相對也不常用。所以,我們今天主要講解的重點是前兩種工廠模式。

簡單工廠(Simple Factory)

// 建立抽象產品類,定義具體產品得公共介面
abstract class Product{
    public abstract void Show();
}
// 建立具體產品類(繼承抽象產品類),定義生產的具體產品

//具體產品類A
class  ProductA extends  Product{
    @Override
    public void Show() {
        System.out.println("生產出了產品A");
    }
}

//具體產品類B
class  ProductB extends  Product{

    @Override
    public void Show() {
        System.out.println("生產出了產品C");
    }
}

// 建立工廠類,通過建立靜態方法從而根據傳入不同引數建立不同具體產品類的例項
class  Factory {
    public static Product Manufacture(String ProductName){
//工廠類裡用switch語句控制生產哪種商品;
//使用者只需要呼叫工廠類的靜態方法就可以實現產品類的例項化。
        switch (ProductName){
            case "A":
                return new ProductA();
            case "B":
                return new ProductB();
            default:
                return null;

        }
    }
}

可以看到,對於上面得簡單工廠的實現方法,如果我們要新增新的Product,那勢必要改動到 Factory 的程式碼,那這是不是違反開閉原則呢?實際上,如果不是需要頻繁地新增新的 Product,只是偶爾修改一下Factory程式碼,稍微不符合開閉原則,也是完全可以接受的。

除此之外,在 Factory 有一組 if 分支判斷邏輯,是不是應該用多型或其他設計模式來替代呢?實際上,如果 if 分支並不是很多,程式碼中有 if 分支也是完全可以接受的。應用多型或設計模式來替代 if 分支判斷邏輯,也並不是沒有任何缺點的,它雖然提高了程式碼的擴充套件性,更加符合開閉原則,但也增加了類的個數,犧牲了程式碼的可讀性。

總結一下,儘管簡單工廠模式的程式碼實現中,有多處 if 分支判斷邏輯,違背開閉原則,但權衡擴充套件性和可讀性,這樣的程式碼實現在大多數情況下(比如,不需要頻繁地新增 Product,也沒有太多的 Product)是沒有問題的。

工廠方法(Factory Method)

//建立抽象工廠類,定義具體工廠的公共介面
abstract class IFactory{
    public abstract Product Manufacture();
}

//工廠A類 - 生產A類產品
class  FactoryA extends IFactory{
    @Override
    public Product Manufacture() {
        return new ProductA();
    }
}

//工廠B類 - 生產B類產品
class  FactoryB extends IFactory{
    @Override
    public Product Manufacture() {
        return new ProductB();
    }
}

實際上,這就是工廠方法模式的典型程式碼實現。這樣當我們新增一種 Product 的時候,只需要新增一個實現了 IFactory 介面的 Factory 類即可。所以,工廠方法模式比起簡單工廠模式更加符合開閉原則。

看似很完美了,但是這裡還是有一些小問題,簡單工廠只是把麻煩的if判斷從工廠類,移交到了使用者手裡,怎麼說呢?來看下面這段程式碼

//生產工作流程
public class FactoryPattern {
    public static void main(String[] args){
        //客戶要產品A
        Product mFactoryA = load("A");
        mFactoryA.Manufacture().Show();

        //客戶要產品B
        Product mFactoryB = load("A");
        mFactoryB.Manufacture().Show();
    }
    
    

  public IFactory load(String factoryType) {

    IFactory factory = null;
    if ("A".equalsIgnoreCase(factoryType)) {
      factory = new FactoryA();
    } else if ("B".equalsIgnoreCase(factoryType)) {
      factory = new FactoryB();
    } else {
      throw new InvalidRuleConfigException("factoryType is not supported: " + factoryType);
    }

    return factory;
  }
}

從上面的程式碼實現來看,工廠類物件的建立邏輯又耦合進了 load() 函式中,跟我們最初的程式碼版本非常相似。假如我們的業務需求是,根據讀取的配置檔案來建立相應的Product,其實不管是簡單工廠模式還是方法工廠模式,上訴的if判斷都無法消滅掉(所以在 GoF 的《設計模式》一書中,它將簡單工廠模式看作是工廠方法模式的一種特例

抽象工廠(Abstract Factory)

講完了簡單工廠、工廠方法,我們再來看抽象工廠模式。抽象工廠模式的應用場景比較特殊,沒有前兩種常用,不是我們學習的重點,我們來簡單瞭解一下就行了。

我們這裡有一個需求場景,有一個工廠,他主要生產容器和模具。對於容器和模具下面又分很多種類。如下:

// 容器
ContainerProductA 
ContainerProductB

//模具
MouldProductA
MouldProductB

針對這種特殊的場景,如果還是繼續用工廠方法來實現的話,我們要針對每個產品 都編寫一個工廠類,也就是要編寫 4 個工廠類。如果我們未來還需要增加了其他型別的生產產品(比如 杯子),那就要再對應地增加 2 個工廠類。而我們知道,過多的類也會讓系統難維護。這個問題該怎麼解決呢?

抽象工廠就是針對這種非常特殊的場景而誕生的。我們可以讓一個工廠負責建立多個不同型別的物件(容器、模具、杯子 等),而不是隻建立一種 Product 物件。這樣就可以有效地減少工廠類的個數。具體的程式碼實現如下所示:

//建立抽象工廠類,定義具體工廠的公共介面
abstract class Factory{
   public abstract Product ManufactureContainer();
    public abstract Product ManufactureMould();
}
//建立抽象產品族類 ,定義具體產品的公共介面;
abstract class AbstractProduct{
    public abstract void Show();
}
//容器產品抽象類
abstract class ContainerProduct extends AbstractProduct{
    @Override
    public abstract void Show();
}

//模具產品抽象類
abstract class MouldProduct extends AbstractProduct{
    @Override
    public abstract void Show();
}

//容器產品A類
class ContainerProductA extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生產出了容器產品A");
    }
}

//容器產品B類
class ContainerProductB extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生產出了容器產品B");
    }
}

//模具產品A類
class MouldProductA extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生產出了模具產品A");
    }
}

//模具產品B類
class MouldProductB extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生產出了模具產品B");
    }
}
//A廠 - 生產模具+容器產品
class FactoryA extends Factory{

    @Override
    public Product ManufactureContainer() {
        return new ContainerProductA();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductA();
    }
}

//B廠 - 生產模具+容器產品
class FactoryB extends Factory{

    @Override
    public Product ManufactureContainer() {
        return new ContainerProductB();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductB();
    }
}

總結

我們來一塊總結回顧一下,這裡的三種工廠模式中,簡單工廠和工廠方法比較常用,抽象工廠的應用場景比較特殊,所以很少用到,其實瞭解一下就可以了。

當建立邏輯比較複雜,是一個“大工程”的時候,我們就考慮使用工廠模式,封裝物件的建立過程,將物件的建立和使用相分離。何為建立邏輯比較複雜呢?

  • 程式碼中如果存在 if-else分支判斷,動態地根據不同的型別建立不同的物件。針對這種情況,我們就考慮使用工廠模式,將這一大坨if-else建立物件的程式碼抽離出來,放到工廠類中。
  • 還有一種情況,就是物件的建立十分複雜,我們也可以使用工廠類的方式來遮蔽物件的建立細節(建造者模式也是解決該類問題),在這種情況下,我們也可以考慮使用工廠模式,將物件的建立過程封裝到工廠類中

對於第一種情況,當每個物件的建立邏輯都比較簡單的時候,推薦使用簡單工廠模式,將多個物件的建立邏輯放到一個工廠類中。

當每個物件的建立邏輯都比較複雜的時候,為了避免設計一個過於龐大的簡單工廠類,推薦使用工廠方法模式,將建立邏輯拆分得更細,每個物件的建立邏輯獨立到各自的工廠類中。

還是那句話,不要為了使用設計模式而用設計模式,設計模式都是在相應的使用場景下而誕生的。

相關文章