設計模式(四)工廠模式

weixin_34337265發表於2017-12-12

我們在第一次學習物件這個概念的時候,新建物件用的是這樣的程式碼:

Product product = new Product();

但是真正到了工程上這樣的程式碼是“生硬”的,當遇到 Product 改變,或者 Product 初始化流程更改這樣的新需求時,要改變的程式碼量相當的大。我們需要在找出所有呼叫 Product 構造器的地方,併為之新增上新的程式碼,這樣的程式碼設計顯然是不符合設計原則的。

為了改善上面的情況,工廠模式應運而生。

工廠模式是一種建立模式。建立模式對類的例項化過程進行了抽象,能將類的建立和使用分離。工廠類將產品類的初始化封裝在方法之中,對於外界而言,只要知道產品類的公共介面,就可以正常實現功能,這使得各個模組開發時只專注於自己的業務邏輯,忽略物件的建立細節。

由於場景不同,我們可以看到不同形態的工廠模式,一般可以分為以下幾種:

    1. 簡單工廠模式
    1. 工廠方法模式
    1. 抽象工廠模式

三種模式間並沒有絕對的優劣,因為各個模式各有優缺,需要根據特定的場景來選擇。但是大多數開發場景中,我們並沒有這樣龐大的系統,因此,通常我們使用的還是前兩種方法。

一、簡單工廠模式

簡單工廠模式是一種類建立模式,通常情況下,簡單工廠的物件會有一個靜態方法用於建立產品,並且這個方法通常根據傳入的引數來生產指定的產品。這種工廠模式即使你不知道有設計模式這一說法,也會自行使用,因為它構造簡單,實現方便,也確實能在專案中實現立竿見影的效果。

1)簡單工廠模型

5419569-6c7ad500bba5aa70.png
SimpleFactory.png

先來看看最簡單的實現程式碼:

public class SimpleFactory {
    
    public static Product createProduct(String arg){
        Product product = null;
        switch (arg) {
            case "A":
                product = new ProductA();
                
                // init something
                
                break;
            case "B":
                product = new ProductB();

                // init something
                
                break;
            default:
                throw new RuntimeException("product not exist for arg: "+arg);
        }
        return product;
    }
    
}

2)使用場景
對於產品種類少,且產品相對固定,出現新種類產品的可能性小的時候,可以使用簡單工廠模式

我們假定一個簡單的場景,依舊用上一篇建造者模式中提到的電腦。

現在電腦都可以辦公和學習,因此,我們定義一個公共介面:

public interface IComputer {

    void work();

    void study();
    
}

然後,現在一個人有一臺電腦用來工作或者學習,但是這臺電腦是有配置高低分別的,這時候我們需要兩個類實現上面的介面,並且在構造器裡,分別配置它的硬體資訊:

public class ComputerA implements IComputer {
    private static final String TAG = "ComputerA";

    public ComputerA(){
        //初始化高階硬體配置
        Log.i(TAG, "ComputerA: 初始化高階硬體配置");
    }

    @Override
    public void work() {
        //高階機工作
        Log.i(TAG, "ComputerA: 高階機工作");
    }

    @Override
    public void study() {
        //高階機玩學習
        Log.i(TAG, "ComputerA: 高階機玩學習");
    }
}

public class ComputerB implements IComputer {
    private static final String TAG = "ComputerB";
    
    public ComputerB(){
        //初始化普通硬體配置
        Log.i(TAG, "ComputerA: 初始化普通硬體配置");
    }

    @Override
    public void work() {
        //普通機工作
        Log.i(TAG, "ComputerA: 普通機工作");
    }

    @Override
    public void study() {
        //普通機玩學習
        Log.i(TAG, "ComputerA: 普通機玩學習");
    }
}

通過構造器,我們已經可以獲取到不同硬體配置的電腦,然後我們需要對不同電腦安裝適合的工作學習軟體:

public class SimpleFactory {

    public static IComputer createProduct(String arg){
        IComputer product = null;
        switch (arg) {
            case "A":
                product = new ComputerA();
                // 下載學習軟體 M
                // 下載工作軟體 N
                // init something

                break;
            case "B":
                product = new ComputerB();
                // 下載學習軟體 X
                // 下載工作軟體 Y
                // init something

                break;
            default:
                throw new RuntimeException("product not exist for arg: "+arg);
        }
        return product;
    }

}

下面在想要工作學習的地方,我們只需要傳入所需電腦對應的名稱,就可以獲取我們需要的電腦並且實現我們工作學習的目的:

IComputer iComputerA = SimpleFactory.createProduct("A");
iComputerA.study();
iComputerA.work();

IComputer iComputerB = SimpleFactory.createProduct("B");
iComputerB.study();
iComputerB.work();

下面是這個過程的日誌圖:


5419569-64ec3136880a5ffa.png
效果圖-g.png

對比程式碼和日誌,我們可以看到,由於簡單工廠的封裝,在需要使用電腦工作學習的時候,我們只要知道當前需要的電腦名稱:“A” 或 “B”,就可以獲取我們的電腦,並且通過統一的介面方法實現工作學習的目的。

這樣做,顯然使得程式碼變得更加簡潔,對於業務而言邏輯也變得清楚,因為無關的東西都被隱藏了起來。

3)優缺點
**優點:
構造容易、邏輯簡單。只要通過一個工廠方法就可以得到需要的物件,不用關注這個物件是如何構造出來。工廠模式能略過了產品的初始化細節,並且規範統一了所有產品功能的呼叫方法。

**缺點:
由於簡單工廠所有產品的初始化方法都在一個方法中,當產品數量過多且初始化過程複雜的時候,程式碼會顯得過長且難以理解。同時,通過標誌位分辨生產產品的型別,使得程式碼內部耦合嚴重,如果需要新新增一個產品 C ,就必須要修改這個工廠類物件,這顯然不符合前面提到的 開放封閉原則(ASD) ,即對擴充開放,對修改封閉。優秀的設計應該能夠在有新產品的時候,儘量保持原始碼不變,只新增新程式碼就能實現功能。這一點,簡單工廠模式無法達到。

那麼這一點,我們需要怎樣達到呢?於是,工廠方法模式出現了。

二、工廠方法模式

工廠方法模式也是一種類建立模式,與簡單工廠不同的是,它不再通過引數來區分當前生產的產品物件。工廠方法模式將工廠抽象化,定義了一個公共父類用於規定建立產品物件的公共介面,並把所有產品實力化的時間延遲到工廠的子類。文字說明有些不好理解,先來看一下圖解。

5419569-93b8c761fcee824b.png
MethodFactory.png

由圖可以看到,對應產品 A 有指定的 FactoryA 用於生產,對應產品 B 有指定的 FactoryB 來生產。FactoryA 和 FactoryB 都是 IFactory 的子類,如果想要生產新的產品 C ,只需要多加一個 IFactory 子類用於生產 C ,而不需要改動原有程式碼。因此這種模式,也叫做多型工廠模式。

還是用上面的電腦例子,現在我們要多加一個生產工廠,生產低端電腦:

5419569-74c904029baa97db.png
MethodFactory2.png

工廠方法模式遵循[開放封閉原則(ASD)],但是它也有缺點,即完成相同的功能,對於工廠方法而言要新增一個工廠類一個產品類,檔案數量多,使得系統變得複雜。

而現在問題又來了,高階、中端、低端電腦都有了,但是實際上,對於電腦我們不只有配置高低,還有品牌。現在高階、中端、低端電腦提供商有兩家,一家是聯想,一家是巨集基。在這樣的情況下, 工廠方法模式的設計並沒有辦法讓我們獲取我們需要的品牌的電腦。於是,我們用到了抽象工廠模式。

三、抽象工廠模式

抽象工廠模式是對工廠模式的抽象。事實上,抽象工廠模式是最具有一般性質的工廠模式。為了瞭解這個最為抽象的模式,首先,我們需要了解幾個概念,我們還是會以熟悉的電腦為例子進行說明:

  • **產品繼承結構:例如高階電腦是一個類別,而聯想高階電腦和巨集基高階電腦,都是這個類別的子類,實現了它的功能介面。
  • **產品族:產品族是指由同一個工廠生產的,位於不同產品等級結構中的一組產品。這裡是指同個品牌下的一系列產品,例如聯想有高階、中端、低端的電腦產生流水線,同樣的巨集基也有,而這高階、中端、低端的三種電腦,就是他們的產品族

在這裡,例子中的高階、中端和低端更換為商務本、遊戲本和二合一的平板筆記本可能更合適些。不過,管他呢。

一個抽象工廠模式一般包含如下角色:
AbstractFactory:抽象工廠
ConcreteFactory:具體工廠
AbstractProduct:抽象產品
Product:具體產品

先看下圖解:


5419569-42e976d0065e2e0c.png
AbstractFactory.png

可以看到,不同品牌的生產工廠分別實現了抽象工廠的介面,將自己的生產過程封裝起來,在我們需要電腦工作學習的時候,只需要根據需要的品牌選取合適的工廠類,然後利用工廠類選擇合適的機型,就可以獲取我們需要的電腦物件:

public interface IComputerFactory {
    IComputer createComputerA();
    IComputer createComputerB();
}

public class LenovoComputerFactory implements IComputerFactory {
    private static final String TAG = "LenovoComputerFactory";

    public LenovoComputerFactory(){
        Log.i(TAG, "LenovoComputerFactory: 聯想電腦工廠初始化");
    }
    @Override
    public IComputer createComputerA() {
        Log.i(TAG, "createComputerA: 聯想電腦工廠製造高階配置電腦");
        return new ComputerA();
    }

    @Override
    public IComputer createComputerB() {
        Log.i(TAG, "createComputerB: 聯想電腦工廠製造普通配置電腦");
        return new ComputerB();
    }
}

呼叫日誌列印:


5419569-bd90c0dab301db02.png
效果圖-g.png

抽象工廠模式的優點在於,新新增產品族是符合[開放封閉原則(ASD)],但是新增產品的繼承結構卻是不符合的,在具體專案中需要權衡傾向,再作應用。

感謝:

  1. android_interview - builder_pattern
  2. UML類圖與類的詳解

以上。

相關文章