工廠模式解讀

3年工作經驗是吧?

你知道工廠模式分爲幾類嗎?他們都有什麼區別?

那你說說你們項目中是怎麼使用工廠模式的?

帶着問題,尤其是面試問題的學習纔是最高效的。加油,奧利給!

文章收錄在 GitHub JavaEgg ,N線互聯網開發必備技能兵器譜

工廠模式

工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。

工廠模式可以分爲三類:

  • 簡單工廠模式(Simple Factory)
  • 工廠方法模式(Factory Method)
  • 抽象工廠模式(Abstract Factory)

簡單工廠其實不是一個標準的的設計模式。GOF 23種設計模式中只有「工廠方法模式」與「抽象工廠模式」。簡單工廠模式可以看爲工廠方法模式的一種特例,爲了統一整理學習,就都歸爲工廠模式。

這三種工廠模式在設計模式的分類中都屬於創建型模式,三種模式從上到下逐步抽象。

創建型模式

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

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

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

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

工廠模式優點:

  • 可以使代碼結構清晰,有效地封裝變化。在編程中,產品類的實例化有時候是比較複雜和多變的,通過工廠模式,將產品的實例化封裝起來,使得調用者根本無需關心產品的實例化過程,只需依賴工廠即可得到自己想要的產品。
  • 對調用者屏蔽具體的產品類。如果使用工廠模式,調用者只關心產品的接口就可以了,至於具體的實現,調用者根本無需關心。即使變更了具體的實現,對調用者來說沒有任何影響。
  • 降低耦合度。產品類的實例化通常來說是很複雜的,它需要依賴很多的類,而這些類對於調用者來說根本無需知道,如果使用了工廠方法,我們需要做的僅僅是實例化好產品類,然後交給調用者使用。對調用者來說,產品所依賴的類都是透明的。

適用場景

不管是簡單工廠模式,工廠方法模式還是抽象工廠模式,他們具有類似的特性,所以他們的適用場景也是類似的。

首先,作爲一種創建類模式,在任何需要生成複雜對象的地方,都可以使用工廠方法模式。有一點需要注意的地方就是複雜對象適合使用工廠模式,而簡單對象,特別是只需要通過new就可以完成創建的對象,無需使用工廠模式。如果使用工廠模式,就需要引入一個工廠類,會增加系統的複雜度。

其次,工廠模式是一種典型的解耦模式,迪米特法則在工廠模式中表現的尤爲明顯。假如調用者自己組裝產品需要增加依賴關係時,可以考慮使用工廠模式。將會大大降低對象之間的耦合度。

再次,由於工廠模式是依靠抽象架構的,它把實例化產品的任務交由實現類完成,擴展性比較好。也就是說,當需要系統有比較好的擴展性時,可以考慮工廠模式,不同的產品用不同的實現工廠來組裝。


一、簡單工廠模式

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

現在我們要使用面向對象的形式定義計算器,爲了實現各算法之間的解耦。我們一般會這麼寫:

// 計算類的基類
@Setter
@Getter
public abstract class Operation {
    private double value1 = 0;
    private double value2 = 0;
    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 static void main(String[] args) {
  //計算兩數之和
  OperationAdd operationAdd = new OperationAdd();
  operationAdd.setValue1(1);
  operationAdd.setValue2(2);
  System.out.println("sum:"+operationAdd.getResule());
  //計算兩數乘積
  OperationMul operationMul = new OperationMul();
  operationMul.setValue1(3);
  operationMul.setValue2(5);
  System.out.println("multiply:"+operationMul.getResule());
  //計算兩數之差。。。
}

想要使用不同的運算的時候就要創建不同的類,並且要明確知道該類的名字。那麼這種重複的創建類的工作其實可以放到一個統一的類中去管理。這樣的方法我們就叫做「簡單工廠模式」,在簡單工廠模式中用於創建實例的方法是靜態(static)方法,因此簡單工廠模式又被稱爲「靜態工廠方法」模式。。簡單工廠模式有以下優點:

  • 一個調用者想創建一個對象,只要知道其名稱就可以了。
  • 屏蔽產品的具體實現,調用者只關心產品的接口。

1.1 定義

提供一個創建對象實例的功能,而無需關心其具體實現。被創建實例的類型可以是接口、抽象類,也可以是具體的類。

1.2 簡單工廠模式實現方式

沒騙你,簡單工廠模式,真是因爲簡單才被叫做簡單工廠模式的。

簡單工廠模式包含 3 個角色(要素):

  • Factory:即工廠類, 簡單工廠模式的核心部分,負責實現創建所有產品的內部邏輯;工廠類可以被外界直接調用,創建所需對象
  • Product:抽象類產品, 它是工廠類所創建的所有對象的父類,封裝了各種產品對象的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因爲所有創建的具體產品對象都是其子類對象
  • ConcreteProduct:具體產品, 它是簡單工廠模式的創建目標,所有被創建的對象都充當這個角色的某個具體類的實例。它要實現抽象產品中聲明的抽象方法

UML類圖

實例

現在我們定義一個工廠類,它可以根據參數的不同返回不同類的實例,被創建的實例通常都具有共同的父類。

//工廠類
public class OperationFactory {

    public static Operation createOperation(String operation) {
        Operation oper = null;
        switch (operation) {
            case "add":
                oper = new OperationAdd();
                break;
            case "sub":
                oper = new OperationSub();
                break;
            case "mul":
                oper = new OperationMul();
                break;

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

有了工廠類之後,可以使用工廠創建對象:

public static void main(String[] args) {
  Operation operationAdd = OperationFactory.createOperation("add");
  operationAdd.setValue1(1);
  operationAdd.setValue2(2)
  System.out.println(operationAdd.getResule());
}

通過簡單工廠模式,該計算器的使用者不需要關係實現加法邏輯的那個類的具體名字,只要知道該類對應的參數"add"就可以了。這就體現了之前提到的工廠模式的優點。

1.3 簡單工廠模式存在的問題

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

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

1.4 簡單工廠模式總結

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

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

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

爲了解決這些缺點,就有了工廠方法模式。


二、工廠方法模式

我們常說的工廠模式,就是指「工廠方法模式」,也叫「虛擬構造器模式」或「多態工廠模式」。

2.1 定義

定義一個創建對象的接口,但讓實現這個接口的類來決定實例化哪個類。工廠方法讓類的實例化推遲到子類中進行

2.2 工廠方法模式實現方式

工廠方法模式包含 4 個角色(要素):

  • Product:抽象產品,定義工廠方法所創建的對象的接口,也就是實際需要使用的對象的接口
  • ConcreteProduct:具體產品,具體的Product接口的實現對象
  • Factory:工廠接口,也可以叫 Creator(創建器),申明工廠方法,通常返回一個 Product 類型的實例對象
  • ConcreteFactory:工廠實現,或者叫 ConcreteCreator(創建器對象),覆蓋 Factory 定義的工廠方法,返回具體的 Product 實例

UML類圖

實例

從UML類圖可以看出,每種產品實現,我們都要增加一個繼承於工廠接口 IFactory 的工廠類 Factory ,修改簡單工廠模式代碼中的工廠類如下:

//工廠接口
public interface IFactory {
    Operation CreateOption();
}

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

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

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

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

這時,我們使用計算器的時候,要爲每種運算方法增加一個工廠對象

public class Client {
    public static void main(String[] args) {
      //減法
      IFactory subFactory = new SubFactory();
      Operation operationSub =  subFactory.CreateOption();
      operationSub.setValue1(22);
      operationSub.setValue2(20);
      System.out.println("sub:"+operationSub.getResult());
      //除法
      IFactory Divfactory = new DivFactory();
      Operation operationDiv =  Divfactory.CreateOption();
      operationDiv.setValue1(99);
      operationDiv.setValue2(33);
      System.out.println("div:"+operationSub.getResult());
    }
}

納尼,這不是更復雜了嗎,每個產品對應一個工廠,我又不是按代碼量賺錢的。。。

2.3 工廠方法模式適用場景

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

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

  • 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
  • 一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
  • 將創建對象的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。

使用場景

  • 日誌記錄器:記錄可能記錄到本地硬盤、系統事件、遠程服務器等,用戶可以選擇記錄日誌到什麼地方。
  • 數據庫訪問,當用戶不知道最後系統採用哪一類數據庫,以及數據庫可能有變化時。
  • 設計一個連接服務器的框架,需要三個協議,“POP3”、“IMAP”、“HTTP”,可以把這三個作爲產品類,共同實現一個接口。
  • 比如 Hibernate 換數據庫只需換方言和驅動就可以

2.4 工廠方法模式總結

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

由於使用了面向對象的多態性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。

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

優點:

  • 一個調用者想創建一個對象,只要知道其名稱就可以了。
  • 擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。
  • 屏蔽產品的具體實現,調用者只關心產品的接口。

缺點:

每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這並不是什麼好事。


三、抽象工廠模式

工廠方法模式通過引入工廠等級結構,解決了簡單工廠模式中工廠類職責太重的問題,但由於工廠方法模式中的每個工廠只生產一類產品,可能會導致系統中存在大量的工廠類,勢必會增加系統的開銷。此時,我們可以考慮將一些相關的產品組成一個“產品族”,由同一個工廠來統一生產,這就是抽象工廠模式的基本思想。

3.1 定義

爲創建一組相關或相互依賴的對象提供一個接口,而且無需指定他們的具體類。

抽象工廠(Abstract Factory)模式,又稱工具箱(Kit 或Toolkit)模式。

3.2 抽象工廠模式實現方式

抽象工廠模式是工廠方法模式的升級版本,他用來創建一組相關或者相互依賴的對象。他與工廠方法模式的區別就在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則是針對的多個產品等級結構。在編程中,通常一個產品結構,表現爲一個接口或者抽象類,也就是說,工廠方法模式提供的所有產品都是衍生自同一個接口或抽象類,而抽象工廠模式所提供的產品則是衍生自不同的接口或抽象類。

在抽象工廠模式中,有一個產品族的概念:所謂的產品族,是指位於不同產品等級結構中功能相關聯的產品組成的家族。抽象工廠模式所提供的一系列產品就組成一個產品族;而工廠方法提供的一系列產品稱爲一個等級結構。

也沒騙你,抽象工廠模式確實是抽象。

抽象工廠模式包含的角色(要素):

  • AbstractFactory:抽象工廠,用於聲明生成抽象產品的方法
  • ConcreteFactory:具體工廠,實現抽象工廠定義的方法,具體實現一系列產品對象的創建
  • AbstractProduct:抽象產品,定義一類產品對象的接口
  • ConcreteProduct:具體產品,通常在具體工廠裏,會選擇具體的產品實現,來創建符合抽象工廠定義的方法返回的產品類型的對象。
  • Client:客戶端,使用抽象工廠來獲取一系列所需要的產品對象

UML類圖

實例

我把維基百科的例子改下用於理解,假設我們要生產兩種產品,鍵盤(Keyboard)和鼠標(Mouse) ,每一種產品都支持多種系列,比如 Mac 系列和 Windows 系列。這樣每個系列的產品分別是 MacKeyboard WinKeyboard, MacMouse, WinMouse 。爲了可以在運行時刻創建一個系列的產品族,我們可以爲每個系列的產品族創建一個工廠 MacFactory 和 WinFactory 。每個工廠都有兩個方法 CreateMouse 和 CreateKeyboard 並返回對應的產品,可以將這兩個方法抽象成一個接口 HardWare 。這樣在運行時刻我們可以選擇創建需要的產品系列。

  1. 抽象產品

  2. public interface Keyboard {
      void input();
    }
    public interface Mouse {
      void click();
    }
    
  3. 具體產品

    //具體產品
    public class MacKeyboard implements Keyboard {
        @Override
        public void input() {
            System.out.println("Mac 專用鍵盤");
        }
    }
    
    public class MacMouse implements Mouse {
        @Override
        public void click() {
            System.out.println("Mac 專用鼠標");
        }
    }
    
    public class WinKeyboard implements Keyboard {
        @Override
        public void input() {
            System.out.println("Win 專用鍵盤");
        }
    }
    
    public class WinMouse implements Mouse {
        @Override
        public void click() {
            System.out.println("win 專用鼠標");
        }
    }
    
  4. 抽象工廠

    public interface Hardware {
         Keyboard createKyeBoard();
         Mouse createMouse();
    }
    
  5. 具體的工廠類

    public class MacFactory implements Hardware{
        @Override
        public Keyboard createKyeBoard() {
            return new MacKeyboard();
        }
    
        @Override
        public Mouse createMouse() {
            return new MacMouse();
        }
    }
    
    public class WinFactory implements Hardware{
        @Override
        public Keyboard createKyeBoard() {
            return new WinKeyboard();
        }
    
        @Override
        public Mouse createMouse() {
            return new WinMouse();
        }
    }
    
  6. 使用

    public class Client {
      public static void main(String[] args) {
        Hardware macFactory = new MacFactory();
        Keyboard keyboard = macFactory.createKyeBoard();
        keyboard.input();   //Mac 專用鍵盤
    
        Hardware winFactory = new WinFactory();
        Mouse mouse = winFactory.createMouse();
        mouse.click();  //win 專用鼠標
      }
    }
    

3.3 抽象工廠模式適用場景

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

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

  • 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是重要的。
  • 系統中有多於一個的產品族,而每次只使用其中某一產品族。
  • 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。
  • 系統結構穩定,不會頻繁的增加對象。

“開閉原則”的傾斜性

在抽象工廠模式中,增加新的產品族很方便,但是增加新的產品等級結構很麻煩,抽象工廠模式的這種性質稱爲**“開閉原則”的傾斜性**。“開閉原則”要求系統對擴展開放,對修改封閉,通過擴展達到增強其功能的目的,對於涉及到多個產品族與多個產品等級結構的系統,其功能增強包括兩方面:

  • 增加產品族:對於增加新的產品族,工廠方法模式很好的支持了“開閉原則”,對於新增加的產品族,只需要對應增加一個新的具體工廠即可,對已有代碼無須做任何修改。
  • 增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,違背了“開閉原則”。

正因爲抽象工廠模式存在“開閉原則”的傾斜性,它以一種傾斜的方式來滿足“開閉原則”,爲增加新產品族提供方便,但不能爲增加新產品結構提供這樣的方便,因此要求設計人員在設計之初就能夠全面考慮,不會在設計完成之後向系統中增加新的產品等級結構,也不會刪除已有的產品等級結構,否則將會導致系統出現較大的修改,爲後續維護工作帶來諸多麻煩。

3.4 抽象工廠模式總結

抽象工廠模式是工廠方法模式的進一步延伸,由於它提供了功能更爲強大的工廠類並且具備較好的可擴展性,在軟件開發中得以廣泛應用,尤其是在一些框架和API類庫的設計中,例如在Java語言的AWT(抽象窗口工具包)中就使用了抽象工廠模式,它使用抽象工廠模式來實現在不同的操作系統中應用程序呈現與所在操作系統一致的外觀界面。抽象工廠模式也是在軟件開發中最常用的設計模式之一。

優點:

  • 抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什麼被創建。由於這種隔離,更換一個具體工廠就變得相對容易,所有的具體工廠都實現了抽象工廠中定義的那些公共接口,因此只需改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行爲。
  • 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。
  • 增加新的產品族很方便,無須修改已有系統,符合“開閉原則”。

缺點:

增加新的產品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層代碼,這顯然會帶來較大的不便,違背了“開閉原則”。

工廠模式的退化

當抽象工廠模式中每一個具體工廠類只創建一個產品對象,也就是隻存在一個產品等級結構時,抽象工廠模式退化成工廠方法模式;當工廠方法模式中抽象工廠與具體工廠合併,提供一個統一的工廠來創建產品對象,並將創建對象的工廠方法設計爲靜態方法時,工廠方法模式退化成簡單工廠模式。

四、我們身邊的工廠模式

工廠模式在Java碼農身邊真是無處不在,不信打開你的項目,搜索 Factory

  • 我們最常用的 Spring 就是一個最大的 Bean 工廠,IOC 通過BeanFactory對Bean 進行管理。

  • 我們使用的日誌門面框架slf4j,點進去就可以看到熟悉的味道

    private final static Logger logger = LoggerFactory.getLogger(HelloWord.class);
    
  • JDK 的 Calendar 使用了簡單工廠模式

     Calendar calendar = Calendar.getInstance();
    

參考

https://blog.csdn.net/lovelion/article/details/17517213

https://wiki.jikexueyuan.com/project/java-design-pattern/abstract-factory-pattern.html

https://blog.csdn.net/lovelion/article/details/17517213

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章