你以為工廠模式很簡單,可能是因為你懂的只是冰山的一角

HollisChuang發表於2019-05-27

GitHub 3.2k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 3.2k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 3.2k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

很多人認為工廠模式很簡單,只是有一個建造工廠,幫我們進行物件構造而已。那麼請嘗試回答下以下問題:

1、工廠模式分為幾類?
2、GOF 23種設計模式中,工廠方法模式和抽象工廠模式有什麼區別?
3、不在GOF 23種設計模式中的簡單工廠模式是什麼?
4、簡單工廠模式、工廠方法模式和抽象工廠模式各自解決什麼問題?有什麼不同?

如果以上四個問題,你都可以很好的回答的話,那麼這篇文章就沒有繼續讀下去的必要了,否則,建議你好好學習下本文。

三種工廠模式

工廠模式可以分為三類:

  • 1)簡單工廠模式(Simple Factory)

  • 2)工廠方法模式(Factory Method)

  • 3)抽象工廠模式(Abstract Factory)

這三種模式從上到下逐步抽象,並且更具一般性。

GOF在《設計模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)。

將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。

這三種工廠模式在設計模式的分類中都屬於建立型模式

建立型模式(Creational Pattern)對類的例項化過程進行了抽象,能夠將軟體模組中物件的建立和物件的使用分離。為了使軟體的結構更加清晰,外界對於這些物件只需要知道它們共同的介面,而不清楚其具體的實現細節,使整個系統的設計更加符合單一職責原則。

建立型模式在建立什麼(What),由誰建立(Who),何時建立(When)等方面都為軟體設計者提供了儘可能大的靈活性。

建立型模式隱藏了類的例項的建立細節,通過隱藏物件如何被建立和組合在一起達到使整個系統獨立的目的。

工廠模式是建立型模式中比較重要的。工廠模式的主要功能就是幫助我們例項化物件的。之所以名字中包含工廠模式四個字,是因為物件的例項化過程是通過工廠實現的,是用工廠代替new操作的。

這樣做的好處是封裝了物件的例項化細節,尤其是對於例項化較複雜或者物件的生命週期應該集中管理的情況。會給你係統帶來更大的可擴充套件性和儘量少的修改量。

接下來我們分別介紹下這三種工廠模式。

簡單工廠模式

簡單工廠模式是屬於建立型模式,又叫做靜態工廠方法(Static Factory Method)模式。簡單工廠模式是由一個工廠物件決定建立出哪一種產品類的例項。簡單工廠模式是工廠模式家族中最簡單實用的模式,可以理解為是不同工廠模式的一個特殊實現。

在介紹簡單工廠模式之前,我們嘗試解決以下問題:

現在我們要使用物件導向的形式定義計算器,為了實現各演算法之間的解耦。主要的用到的類如下:

// 計算類的基類
public abstract class Operation {

    private double value1 = 0;
    private double value2 = 0;

    public double getValue1() {
        return value1;
    }
    public void setValue1(double value1) {
        this.value1 = value1;
    }
    public double getValue2() {
        return value2;
    }
    public void setValue2(double value2) {
        this.value2 = value2;
    }
    protected abstract double getResule();
}

//加法
public class OperationAdd extends Operation {
    @Override
    protected double getResule() {
        return getValue1() + getValue2();
    }
}
//減法
public class OperationSub extends Operation {
    @Override
    protected double getResule() {
        return getValue1() - getValue2();
    }
}
//乘法
public class OperationMul extends Operation {
    @Override
    protected double getResule() {
        return getValue1() * getValue2();
    }
}
//除法
public class OperationDiv extends Operation {
    @Override
    protected double getResule() {
        if (getValue2() != 0) {
            return getValue1() / getValue2();
        }
        throw new IllegalArgumentException("除數不能為零");
    }
}

當我想要執行加法運算時,可以使用如下程式碼:

public class Main {
    public static void main(String[] args) {
        OperationAdd operationAdd = new OperationAdd();
        operationAdd.setValue1(10);
        operationAdd.setValue2(5);
System.out.println(operationAdd.getResule());
    }
}

當我需要執行減法運算時,我就要建立一個OperationSub類。也就是說,我想要使用不同的運算的時候就要建立不同的類,並且要明確知道該類的名字。

那麼這種重複的建立類的工作其實可以放到一個統一的工廠類中。簡單工廠模式有以下優點:

1、一個呼叫者想建立一個物件,只要知道其名稱就可以了。

2、遮蔽產品的具體實現,呼叫者只關心產品的介面。

簡單工廠模式實現方式

簡單工廠模式其實和他的名字一樣,很簡單。先來看看它的組成:

Factory:這是本模式的核心,含有一定的商業邏輯和判斷邏輯。在java中它往往由 一個具體類實現。(OperationFactory)

Product:它一般是具體產品繼承的父類或者實現的介面。在java中由介面或者抽象類來實現。(Operation)

ConcreteProduct:工廠類所建立的物件就是此角色的例項。在java中由一個具體類實現。 來用類圖來清晰的表示下的它們之間的關係(OperationAdd\OperationSub等)

在原有類的基礎上,定義工廠類:

//工廠類
public class OperationFactory {

    public static Operation createOperation(String operation) {
        Operation oper = null;
        switch (operation) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            case "*":
                oper = new OperationMul();
                break;

            case "/":
                oper = new OperationDiv();
                break;
            default:
                throw new UnsupportedOperationException("不支援該操作");
        }
        return oper;
    }
}

有了工廠類之後,可以使用工廠建立物件:

Operation operationAdd = OperationFactory.createOperation("+");
operationAdd.setValue1(10);
operationAdd.setValue2(5);
System.out.println(operationAdd.getResule());

通過簡單工廠模式,該計算器的使用者不需要關係實現加法邏輯的那個類的具體名字,他只要知道該類對應的引數"+"就可以了。

簡單工廠模式存在的問題

當我們需要增加一種計算時,例如開平方。這個時候我們需要先定義一個類繼承Operation類,其中實現平方的程式碼。除此之外我們還要修改OperationFactory類的程式碼,增加一個case。這顯然是違背開閉原則的。可想而知對於新產品的加入,工廠類是很被動的。

我們舉的例子是最簡單的情況。而在實際應用中,很可能產品是一個多層次的樹狀結構。 簡單工廠可能就不太適用了。

簡單工廠模式總結

工廠類是整個簡單工廠模式的關鍵。包含了必要的邏輯判斷,根據外界給定的資訊,決定究竟應該建立哪個具體類的物件。通過使用工廠類,外界可以從直接建立具體產品物件的尷尬局面擺脫出來,僅僅需要負責“消費”物件就可以了。而不必管這些物件究竟如何建立及如何組織的。明確了各自的職責和權利,有利於整個軟體體系結構的優化。

但是由於工廠類集中了所有例項的建立邏輯,違反了高內聚責任分配原則,將全部建立邏輯集中到了一個工廠類中;它所能建立的類只能是事先考慮到的,如果需要新增新的類,則就需要改變工廠類了。

當系統中的具體產品類不斷增多時候,可能會出現要求工廠類根據不同條件建立不同例項的需求.這種對條件的判斷和對具體產品型別的判斷交錯在一起,很難避免模組功能的蔓延,對系統的維護和擴充套件非常不利;

這些缺點在工廠方法模式中得到了一定的解決。

工廠方法模式

工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多型工廠(Polymorphic Factory)模式,它屬於類建立型模式。

工廠方法模式是一種實現了“工廠”概念的物件導向設計模式。就像其他建立型模式一樣,它也是處理在不指定物件具體型別的情況下建立物件的問題。

工廠方法模式的實質是“定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類。工廠方法讓類的例項化推遲到子類中進行。”

工廠方法模式用途

工廠方法模式和簡單工廠模式雖然都是通過工廠來建立物件,他們之間最大的不同是——工廠方法模式在設計上完全完全符合“開閉原則”。

在以下情況下可以使用工廠方法模式:

一個類不知道它所需要的物件的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品物件由具體工廠類建立;客戶端需要知道建立具體產品的工廠類。

一個類通過其子類來指定建立哪個物件:在工廠方法模式中,對於抽象工廠類只需要提供一個建立產品的介面,而由其子類來確定具體要建立的物件,利用物件導向的多型性和里氏代換原則,在程式執行時,子類物件將覆蓋父類物件,從而使得系統更容易擴充套件。

將建立物件的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類建立產品子類,需要時再動態指定,可將具體工廠類的類名儲存在配置檔案或資料庫中。

工廠方法模式實現方式

工廠方法模式包含如下角色:

Product:抽象產品(Operation

ConcreteProduct:具體產品(OperationAdd)

Factory:抽象工廠(IFactory)

ConcreteFactory:具體工廠(AddFactory)

這裡還用計算器的例子。在保持OperationOperationAddOperationDivOperationSubOperationMul等幾個方法不變的情況下,修改簡單工廠模式中的工廠類(OperationFactory)。替代原有的那個"萬能"的大工廠類,這裡使用工廠方法來代替:

//工廠介面
public interface IFactory {
    Operation CreateOption();
}

//加法類工廠
public class AddFactory implements IFactory {
    public Operation CreateOption() {
        return new OperationAdd();
    }
}

//除法類工廠
public class DivFactory implements IFactory {
    public Operation CreateOption() {
        return new OperationDiv();
    }
}

//除法類工廠
public class MulFactory implements IFactory {
    public Operation CreateOption() {
        return new OperationMul();
    }
}

//減法類工廠
public class SubFactory implements IFactory {
    public Operation CreateOption() {
        return new OperationSub();
    }
}

這樣,在客戶端中想要執行加法運算時,需要以下方式:

public class Main {

    public static void main(String[] args) {
        IFactory factory = new AddFactory();
        Operation operationAdd =  factory.CreateOption();
        operationAdd.setValue1(10);
        operationAdd.setValue2(5);
        System.out.println(operationAdd.getResult());
    }
}

到這裡,一個工廠方法模式就已經寫好了。

從程式碼量上看,這種工廠方法模式比簡單工廠方法模式更加複雜。針對不同的操作(Operation)類都有對應的工廠。很多人會有以下疑問:

貌似工廠方法模式比簡單工廠模式要複雜的多?

工廠方法模式和我自己建立物件沒什麼區別?為什麼要多搞出一些工廠來?

下面就針對以上兩個問題來深入理解一下工廠方法模式。

為什麼要使用工廠來建立物件?

封裝物件的建立過程

在工廠方法模式中,工廠方法用來建立客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被例項化這一細節,使用者只需要關心所需產品對應的工廠,無須關心建立細節,甚至無須知道具體產品類的類名。

基於工廠角色和產品角色的多型性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定建立何種產品物件,而如何建立這個物件的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多型工廠模式,是因為所有的具體工廠類都具有同一抽象父類。

為什麼每種物件要單獨有一個工廠?

符合『開放-封閉原則』

主要目的是為了解耦。在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的介面,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要新增一個具體工廠和具體產品就可以了。這樣,系統的可擴充套件性也就變得非常好,完全符合“開閉原則。

以上就是工廠方法模式的優點。但是,工廠模式也有一些不盡如人意的地方:

在新增新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和執行,會給系統帶來一些額外的開銷。

由於考慮到系統的可擴充套件性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。

工廠方法模式總結

工廠方法模式是簡單工廠模式的進一步抽象和推廣。

由於使用了物件導向的多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。

在工廠方法模式中,核心的工廠類不再負責所有產品的建立,而是將具體建立工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的介面,而不負責產品類被例項化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。

工廠方法模式的主要優點是增加新的產品類時無須修改現有系統,並封裝了產品物件的建立細節,系統具有良好的靈活性和可擴充套件性;其缺點在於增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,在一定程度上增加了系統的複雜性。

抽象工廠模式

抽象工廠模式(Abstract Factory Pattern):提供一個建立一系列相關或相互依賴物件的介面,而無須指定它們具體的類。抽象工廠模式又稱為Kit模式,屬於物件建立型模式。

抽象工廠模式提供了一種方式,可以將同一產品族的單獨的工廠封裝起來。在正常使用中,客戶端程式需要建立抽象工廠的具體實現,然後使用抽象工廠作為介面來建立這一主題的具體物件。客戶端程式不需要知道(或關心)它從這些內部的工廠方法中獲得物件的具體型別,因為客戶端程式僅使用這些物件的通用介面。抽象工廠模式將一組物件的實現細節與他們的一般使用分離開來。

產品族

來認識下什麼是產品族: 位於不同產品等級結構中,功能相關的產品組成的家族。如下面的例子,就有兩個產品族:跑車族和商務車族。

抽象工廠模式用途

抽象工廠模式和工廠方法模式一樣,都符合開放-封閉原則。但是不同的是,工廠方法模式在增加一個具體產品的時候,都要增加對應的工廠。但是抽象工廠模式只有在新增一個型別的具體產品時才需要新增工廠。也就是說,工廠方法模式的一個工廠只能建立一個具體產品。而抽象工廠模式的一個工廠可以建立屬於一類型別的多種具體產品。工廠建立產品的個數介於簡單工廠模式和工廠方法模式之間。

在以下情況下可以使用抽象工廠模式:

一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有型別的工廠模式都是重要的。

系統中有多於一個的產品族,而每次只使用其中某一產品族。

屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。

系統提供一個產品類的庫,所有的產品以同樣的介面出現,從而使客戶端不依賴於具體實現。

抽象工廠模式實現方式

抽象工廠模式包含如下角色:

AbstractFactory(抽象工廠):用於宣告生成抽象產品的方法

ConcreteFactory(具體工廠):實現了抽象工廠宣告的生成抽象產品的方法,生成一組具體產品,這些產品構成了一個產品族,每一個產品都位於某個產品等級結構中;

AbstractProduct(抽象產品):為每種產品宣告介面,在抽象產品中定義了產品的抽象業務方法;

Product(具體產品):定義具體工廠生產的具體產品物件,實現抽象產品介面中定義的業務方法。

本文的例子採用一個汽車代工廠造汽車的例子。假設我們是一家汽車代工廠商,我們負責給賓士和特斯拉兩家公司製造車子。我們簡單的把賓士車理解為需要加油的車,特斯拉為需要充電的車。其中賓士車中包含跑車和商務車兩種,特斯拉同樣也包含賓士車和商務車。

以上場景,我們就可以把跑車和商務車分別對待,對於跑車有單獨的工廠建立,商務車也有單獨的工廠。這樣,以後無論是再幫任何其他廠商造車,只要是跑車或者商務車我們都不需要再引入工廠。同樣,如果我們要增加一種其他型別的車,比如越野車,我們也不需要對跑車或者商務車的任何東西做修改。

下面是抽象產品,賓士車和特斯拉車:

public interface BenzCar {
    //加汽油
    public void gasUp();

}

public interface TeslaCar {
    //充電
    public void charge();
}

下面是具體產品,賓士跑車、賓士商務車、特斯拉跑車、特斯拉商務車:

public class BenzSportCar implements BenzCar {
    public void gasUp() {
        System.out.println("給我的賓士跑車加最好的汽油");
    }
}

public class BenzBusinessCar implements BenzCar{
    public void gasUp() {
        System.out.println("給我的賓士商務車加一般的汽油");
    }
}

public class TeslaSportCar implements TeslaCar {
    public void charge() {
        System.out.println("給我特斯拉跑車衝滿電");
    }
}

public class TeslaBusinessCar implements TeslaCar {
    public void charge() {
        System.out.println("不用給我特斯拉商務車衝滿電");
    }
}

下面是抽象工廠:

public interface CarFactory {

    public BenzCar getBenzCar();
    public TeslaCar getTeslaCar();
}

下面是具體工廠:

public class SportCarFactory implements CarFactory {
    public BenzCar getBenzCar() {
        return new BenzSportCar();
    }

    public TeslaCar getTeslaCar() {
        return new TeslaSportCar();
    }
}

public class BusinessCarFactory implements CarFactory {
    public BenzCar getBenzCar() {
        return new BenzBusinessCar();
    }

    public TeslaCar getTeslaCar() {
        return new TeslaBusinessCar();
    }
}

“開閉原則”的傾斜性

“開閉原則”要求系統對擴充套件開放,對修改封閉,通過擴充套件達到增強其功能的目的。對於涉及到多個產品族與多個產品等級結構的系統,其功能增強包括兩方面:

增加產品族:對於增加新的產品族,工廠方法模式很好的支援了“開閉原則”,對於新增加的產品族,只需要對應增加一個新的具體工廠即可,對已有程式碼無須做任何修改。

增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,不能很好地支援“開閉原則”。

抽象工廠模式的這種性質稱為“開閉原則”的傾斜性,抽象工廠模式以一種傾斜的方式支援增加新的產品,它為新產品族的增加提供方便,但不能為新的產品等級結構的增加提供這樣的方便。

抽象工廠模式總結

抽象工廠模式提供一個建立一系列相關或相互依賴物件的介面,而無須指定它們具體的類。抽象工廠模式又稱為Kit模式,屬於物件建立型模式。

抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態。

抽象工廠模式的主要優點是隔離了具體類的生成,使得客戶並不需要知道什麼被建立,而且每次可以通過具體工廠類建立一個產品族中的多個物件,增加或者替換產品族比較方便,增加新的具體工廠和產品族很方便;主要缺點在於增加新的產品等級結構很複雜,需要修改抽象工廠和所有的具體工廠類,對“開閉原則”的支援呈現傾斜性。

三種工廠模式對比

簡單工廠模式的優缺點

  • 優點:
    • 1、遮蔽產品的具體實現,呼叫者只關心產品的介面。
    • 2、實現簡單
  • 缺點:
    • 1、增加產品,需要修改工廠類,不符合開放-封閉原則
    • 2、工廠類集中了所有例項的建立邏輯,違反了高內聚責任分配原則

工廠方法模式的優缺點

  • 優點:
    • 1、繼承了簡單工廠模式的優點
    • 2、符合開放-封閉原則
  • 缺點:
    • 1、增加產品,需要增加新的工廠類,導致系統類的個數成對增加,在一定程度上增加了系統的複雜性。

抽象工廠模式的優缺點

  • 優點:
    • 1、隔離了具體類的生成,使得客戶並不需要知道什麼被建立
    • 2、每次可以通過具體工廠類建立一個產品族中的多個物件,增加或者替換產品族比較方便,增加新的具體工廠和產品族很方便;
  • 缺點
    • 增加新的產品等級結構很複雜,需要修改抽象工廠和所有的具體工廠類,對“開閉原則”的支援呈現傾斜性。

簡單工廠 : 用來生產同一等級結構中的任意產品。(對於增加新的產品,主要是新增產品,就要修改工廠類。符合單一職責原則。不符合開放-封閉原則)

工廠方法 :用來生產同一等級結構中的固定產品。(支援增加任意產品,新增產品時不需要更改已有的工廠,需要增加該產品對應的工廠。符合單一職責原則、符合開放-封閉原則。但是引入了複雜性)

抽象工廠 :用來生產不同產品族的全部產品。(增加新產品時,需要修改工廠,增加產品族時,需要增加工廠。符合單一職責原則,部分符合開放-封閉原則,降低了複雜性)

最後,三種工廠模式各有優缺點,沒有最好的,只有最合適的!

相關文章