【設計模式】工廠模式

Nemo&發表於2020-07-09

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

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

簡單工廠也稱為靜態工廠,一般可以用靜態方法(類方法)來獲取物件。

基本介紹

  • 意圖:定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

  • 主要解決:主要解決介面選擇的問題。

  • 何時使用:我們明確地計劃不同條件下建立不同例項時。

  • 如何解決:讓其子類實現工廠介面,返回的也是一個抽象的產品。

  • 關鍵程式碼:建立過程在其子類執行。

  • 應用例項:
    • 您需要一輛汽車,可以直接從工廠裡面提貨,而不用去管這輛汽車是怎麼做出來的,以及這個汽車裡面的具體實現。
    • Hibernate 換資料庫只需換方言和驅動就可以。
  • 優點:
    • 一個呼叫者想建立一個物件,只要知道其名稱就可以了。
    • 擴充套件性高,如果想增加一個產品,只要擴充套件一個工廠類就可以。
    • 遮蔽產品的具體實現,呼叫者只關心產品的介面。
  • 缺點:每次增加一個產品時,都需要增加一個具體類和物件實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這並不是什麼好事。

  • 使用場景:
    • 日誌記錄器:記錄可能記錄到本地硬碟、系統事件、遠端伺服器等,使用者可以選擇記錄日誌到什麼地方。
    • 資料庫訪問,當使用者不知道最後系統採用哪一類資料庫,以及資料庫可能有變化時。
    • 設計一個連線伺服器的框架,需要三個協議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產品類,共同實現一個介面。

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

概括

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

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

  1. 簡單工廠模式是屬於建立型模式,是工廠模式的一種。簡單工廠模式是由一個工廠物件決定建立出哪一種產品類的例項。簡單工廠模式是工廠模式家族中最簡單實用的模式。
  2. 簡單工廠模式:定義了一個建立物件的類,由這個類來封裝例項化物件的行為(程式碼)
  3. 在軟體開發中,當我們會用到大量的建立某種、某類或者某批物件時,就會使用到工廠模式。

一句話概括工廠模式

  • 簡單工廠:一個工廠類,一個產品抽象類。
  • 工廠方法:多個工廠類,一個產品抽象類。
  • 抽象工廠:多個工廠類,多個產品抽象類。

生活中的工廠模式

  • 簡單工廠類:一個麥當勞店,可以生產多種漢堡。
  • 工廠方法類:一個麥當勞店,可以生產多種漢堡。一個肯德基店,也可以生產多種漢堡。
  • 抽象工廠類:百勝餐飲集團下有肯德基和百事公司,肯德基生產漢堡,百事公司生成百事可樂。

其實工廠模式就如它的名字一般,是個密不透風的工廠。我們只需要把我們需要的清單交給它,他就能做出相應的物品給我們,而不需要我們親力親為,親手去製造物品。如 我們需要汽車 car,我們不需要自己new Car()建立一個汽車物件,而是隻需要將 "car"(列舉字串)這種清單交給工廠就可以了。


類圖:
【設計模式】工廠模式

角色分析:

  1. Product:是產品介面
  2. Product1、2、3:是具體產品
  3. Factory:是工廠類,裡面有靜態方法 createProduct(),負責建立不同的產品。

我的理解

其實工廠模式就如它的名字一般,是個密不透風的工廠。我們只需要把我們需要的清單交給它,他就能做出相應的物品給我們,而不需要我們親力親為,親手去製造物品。如 我們需要汽車 car,我們不需要自己new Car()建立一個汽車物件,而是隻需要將 "car"(列舉字串)這種清單交給工廠就可以了。

比如說有個飯館,會做上百種菜,飯館裡面的老闆,還有老顧客,他們當然都知道這個飯館到底賣哪些菜。但是來了一個新來的顧客 他一時間當然不知道這個飯館到底賣哪些菜,這樣的話,我們就需要一個選單來將這些菜的建立給聚合起來,方便管理與查詢。不然的話,上百種菜零零散散 很難管理。
不然的話,新顧客需要點菜就必須得依賴每一種菜他才能點,所以我們需要簡單工廠來幫我們管理、解耦。

比如說我們這個系統需要一個外賣系統和一個現場購買系統,這兩個系統都需要建立披薩,如果重複建立會帶來很多冗餘,所以需要工廠。

工廠也可以充當一個緩衝層來用,將來如果有什麼變化的話,工廠可以起到緩衝的作用,避免因為變化而使系統出現錯誤,
因為緩衝,底層程式碼的修改可以不影響到頂層程式碼的功能。

這是一個樹形的建立型設計模式,和包的作用一樣,希望用到什麼功能就去哪個功能的工廠去找。

個人感覺,這些工廠,抽象工廠,都是為了讓類的宣告、物件的構造更有層次感,就好像樹形圖一樣,以抽象工廠為根,以不同工廠為枝葉,以工廠的產品為葉子。不然一下這個類,一下那個類,會很凌亂。

應用例項

看一個具體的需求
看一個披薩的專案:要便於披薩種類的擴充套件,要便於維護

  1. 披薩的種類很多(比如 GreekPizz、CheesePizz 等)
  2. 披薩的製作有 prepare,bake, cut, box
  3. 完成披薩店訂購功能。

使用傳統方式

  1. 思路分析(類圖)
    【設計模式】工廠模式

  2. 編寫 OrderPizza.java 去訂購需要的各種 Pizza
public class OrderPizza {
    public static void main(String[] args) {

        //我們需要什麼披薩就建立什麼披薩
        //建立芝士披薩
        Pizza pizza = new CheesePizza();
        //Pizza pizza = new PepperPizza();
        //Pizza pizza = new GreekPizza();
    }
}

傳統的方式的優缺點:

  1. 優點:比較好理解,簡單易操作。
  2. 缺點:如果我們有幾十種不同的披薩,我們程式設計師就要硬生生記住這些不同的披薩類名,還不能出錯。
    若伺服器中披薩的類名一改,我們客戶端使用的披薩類名也要改。

改進的思路分析:

  • 分析:修改程式碼可以接受,但是如果我們在其它的地方也有建立 Pizza 的程式碼,就意味著,也需要修改,而建立 Pizza
    的程式碼,往往有多處。
  • 思路:把建立 Pizza 物件封裝到一個類中,這樣我們有新的 Pizza 種類時,只需要修改該類就可,其它有建立到 Pizza
    物件的程式碼就不需要修改了。 => 簡單工廠模式

使用簡單工廠模式

簡單工廠模式的設計方案: 定義一個可以例項化 Pizaa 物件的類,封裝建立物件的程式碼。
簡單工廠模式 也叫 靜態工廠模式,可以將方法靜態化為類方法,直接用類呼叫。

類圖:
【設計模式】工廠模式

程式碼示例:

package com.atguigu.factory.simplefactory.pizzastore.order;

import com.atguigu.factory.simplefactory.pizzastore.pizza.CheesePizza;
import com.atguigu.factory.simplefactory.pizzastore.pizza.GreekPizza;
import com.atguigu.factory.simplefactory.pizzastore.pizza.PepperPizza;
import com.atguigu.factory.simplefactory.pizzastore.pizza.Pizza;


//簡單工廠類
public class SimpleFactory {

    //更加 orderType 返回對應的 Pizza 物件
    public Pizza createPizza(String orderType) {

        Pizza pizza = null;

        System.out.println("使用簡單工廠模式");
        if (orderType.equals("greek")) {
            pizza = new GreekPizza();
            pizza.setName(" 希臘披薩 ");
        } else if (orderType.equals("cheese")) {
            pizza = new CheesePizza();
            pizza.setName(" 乳酪披薩 ");
        } else if (orderType.equals("pepper")) {
            pizza = new PepperPizza();
            pizza.setName("胡椒披薩");
        }

        return pizza;
    }

    //簡單工廠模式 也叫 靜態工廠模式
    public static Pizza createPizza2(String orderType) {

        Pizza pizza = null;

        System.out.println("使用簡單工廠模式 2");
        if (orderType.equals("greek")) {
            pizza = new GreekPizza();
            pizza.setName(" 希臘披薩 ");
        } else if (orderType.equals("cheese")) {
            pizza = new CheesePizza();
            pizza.setName(" 乳酪披薩 ");
        } else if (orderType.equals("pepper")) {
            pizza = new PepperPizza();
            pizza.setName("胡椒披薩");
        }

        return pizza;
    }
}

優化:
我看到網上有很多用反射來使披薩不用去一個一個 if-else。

其實使用反射是一種不錯的辦法,但反射也是從類名反射而不能從類反射!

先看一下工廠模式是用來幹什麼的——屬於建立模式,解決子類建立問題的。換句話來說,呼叫者並不知道執行時真正的類名,只知道從“Circle"可以建立出一個shape介面的類,至於類的名稱是否叫'Circle",呼叫者並不知情。所以真正的對工廠進行擴充套件的方式(防止程式設計師呼叫出錯)可以考慮使用一個列舉類(防止傳入引數時,把circle拼寫錯誤)。

如果呼叫者參肯定型別是Circle的話,那麼其工廠沒有存在的意義了!

比如 IShape shape = new Circle();這樣不是更好?也就是說呼叫者有了Circle這個知識是可以直接呼叫的,根據DP(迪米特法則)其實呼叫者並不知道有一個Circle類的存在,他只需要知道這個IShape介面可以計算圓面積,而不需要知道;圓這個類到底是什麼類名——他只知道給定一個”circle"字串的引數,IShape介面可以自動計算圓的面積就可以了!

其實在.net類庫中存在這個模式的的一個典型的。但他引入的另一個概念“可插入程式設計協議”。

那個就是WebRequest req = WebRequest.Create("http://ccc......");可以自動建立一個HttpWebRequest的物件,當然,如果你給定的是一個ftp地址,他會自動建立一個FtpWebRequest物件。工廠模式中著重介紹的是這種通過某個特定的引數,讓你一個介面去幹對應不同的事而已!而不是呼叫者知道了類!

比如如果圓的那個類名叫"CircleShape“呢?不管是反射還是泛型都干擾了你們具體類的生成!其實這個要說明的問題就是這個,呼叫者(clinet)只知道IShape的存在,在建立時給IShape一個引數"Circle",它可以計算圓的面積之類的工作,但是為什麼會執行這些工作,根據迪米特法則,client是不用知道的。

我想問一下那些寫筆記的哥們,如果你們知道了泛型,那麼為什麼不直接使用呢?幹嗎還需要經過工廠這個類呢?不覺得多餘了嗎?

如果,我只是說如果,如果所有從IShape繼承的類都是Internal型別的呢?而client肯定不會與IShape一個空間!這時,你會了現你根本無法拿到這個類名!

Create時使用序號產生器制是一種簡單的辦法,比如使用一個列舉類,把功能總結到一處。而反射也是一種最簡單的辦法,呼叫者輸入的名稱恰是類名稱或某種規則時使用,比如呼叫者輸入的是Circle,而類恰是CircleShape,那麼可以通過輸入+”Shape"字串形成新的類名,然後從字串將執行類反射出來!

工廠的建立行為,就這些作用,還被你們用反射或泛型轉嫁給了呼叫者(clinet),那麼,這種情況下,要工廠類何用?!

使用列舉類優化

優化一

增加列舉類

public enum PizzaType {
    GREEKPIZZA,
    CHEESEPIZZA,
    PEPPERPIZZA
}

修改工廠類的工廠方法,個人建議工廠方法應該是靜態方法或者採用單例模式:

public class SimpleFactory {
    public static Pizza createPizza(PizzaType type) {
        switch(type) {
            case GREEKPIZZA:
                return new GreekPizza();
            case CHEESEPIZZA:
                return new CheesePizza();
            case PEPPERPIZZA:
                return new PepperPizza();
            default:
                throw new UnknownTypeException();
        }
    }
}

最後是使用示例:

public class FactoryPatternDemo {
    public static void main(String[] args) {
        //獲取 GreekPizza 的物件
        Pizza pizza1 = SimpleFactory.createPizza(PizzaType.GREEKPIZZA);
    }
}

優化二

public enum Factory {
    //使用構造方法,構造列舉型別物件
    GREEKPIZZA(new GreekPizza(),"GREEKPIZZA"),
    CHEESEPIZZA(new CheesePizza(),"CHEESEPIZZA"),
    PEPPERPIZZA(new PepperPizza(),"PEPPERPIZZA");
    
    // 成員變數,上面的構造方法就取決於這些成員變數
    private Pizza pizza;  
    private String name;  
    
    // 普通方法  
    public static Shape getPizza(String name) {  
        for (Factory c : Factory.values()) {  
            if (c.name == name) {  
                return c.pizza;  
            }  
        }  
        return null;  
    } 

    // 構造方法  
    private Factory(Pizza pizza, String name) {  
        this.pizza = pizza;  
        this.name = name;  
    } 
    public String getName() {
        return name;
    }
    public Pizza getPizza() {
        return pizza;
    }
    public void setPizza(Pizza pizza) {
        this.pizza = pizza;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class FactoryPatternDemo {
    public static void main(String[] args) {
        //獲取 GreekPizza 的物件
        /*使用列舉類*/
        Pizza pizza1 = Factory.getPizza("GREEKPIZZA");
        Pizza pizza2 = Factory.getPizza("CHEESEPIZZA");
        Pizza pizza3 = Factory.getPizza("PEPPERPIZZA");
    }
}

工廠模式小結

  1. 工廠模式的意義:
    將例項化物件的程式碼提取出來,放到一個類中統一管理和維護,達到和主專案的依賴關係的解耦。從而提高專案的擴充套件和維護性。
  2. 三種工廠模式(簡單工廠模式、工廠方法模式、抽象工廠模式)
  3. 設計模式的依賴抽象原則
    • 建立物件例項時,不要直接 new 類, 而是把這個 new 類的動作放在一個工廠的方法中,並返回。有的書上說,變數不要直接持有具體類的引用。
    • 不要讓類繼承具體類,而是繼承抽象類或者是實現 interface(介面)
    • 不要覆蓋基類中已經實現的方法。

相關文章