設計模式之工廠模式!深入解析簡單工廠模式,工廠方法模式和抽象工廠模式

攻城獅Chova發表於2022-03-17

工廠模式

  • 建立型模式:

    • 對類的例項化過程進行抽象,能夠將物件的建立和物件的使用分離開來

      • 為了使得軟體的結構更加清晰,外界對於這些物件使用只需要知道共同的介面,而不在意具體實現的細節,這樣使得整個系統更加符合單一職責的原則
      • 建立型模式隱藏了類的例項的建立細節,通過隱藏物件建立和組合過程從而使得整個系統相互獨立的目的
    • 建立型模式在建立什麼,由誰建立,何時建立更加靈活
    • 工廠模式是一個重要的建立型模式,主要功能就是例項化物件
  • 工廠模式: 負責將有共同介面的類例項化

    • 主要解決介面選擇問題
    • 在不同的條件下需要建立不同的例項時使用
    • 工廠模式是一種建立型模式,提供了建立物件的最佳方式
    • 使用工廠模式建立物件不會對客戶端暴露建立邏輯,並且使用一個共同的介面來指向新建立的物件
    • 工廠模式在子類中實現工廠介面,建立過程在子類中執行
  • 工廠模式的分類:

    • 簡單工廠模式Simple Factory
    • 工廠方法模式Factory Method
    • 抽象工廠模式Abstract Factory
  • 工廠模式優點:

    • 可以使得程式碼結構清晰,有效地封裝變化
    • 對呼叫者遮蔽具體的產品類
    • 降低程式碼的耦合度
  • 工廠模式的使用場景:

    • 在任何需要生成複雜物件的地方,都可以使用工廠方法模式.只有複雜的物件才適用於工廠方法模式.對於簡單的只要通過new就可以完成建立的物件,無需使用工廠模式.如果簡單物件使用工廠模式,需要引入一個工廠類,增加系統的複雜度
    • 工廠模式是一種典型的解耦模式,當類之間需要增加依賴關係時,可以使用工廠模式降低系統之間的耦合度
    • 工廠模式是依靠抽象架構的,將例項化的任務交給子類實現,擴充套件性好.當系統需要較好的擴充套件性時,可以使用工廠模式,不同的產品使用不同的工廠來實現組裝

簡單工廠模式

  • 簡單工廠模式Simple Factory Pattern:

    • 定義一個類用於負責建立其餘類的例項,根據自變數的不同返回不同類的例項,被建立的例項通常都有一個共同的父類
    • 簡單工廠模式中用於建立例項的方法時靜態static方法,因此又稱作是靜態工廠方法模式
  • 簡單工廠模式的角色:

    • 工廠類Factory : 簡單工廠模式核心類. 負責建立所有產品的內部邏輯,工廠類可以被外部呼叫,建立所需物件
    • 抽象產品類Product : 工廠類建立的所有物件的父類,封裝產品的共有方法.提高系統的靈活性.使得工廠類只需要定義一個通用的工廠方法,因為所有建立的具體產品都是這個子類物件
    • 具體產品類ConcorrectProduct: 所有被建立的物件都是這個類的具體例項,需要實現抽象產品中宣告的抽象方法
      在這裡插入圖片描述
  • 簡單工廠模式程式碼實現
  • 簡單工廠模式優點:

    • 簡單工廠模式提供了專門的類用於建立物件,實現了對責任的分割. 工廠類Factory中含有必要的判斷邏輯,決定建立具體產品類ConcreteProduct的例項,客戶端只需要消費產品
    • 客戶端不需要知道需要建立的具體產品類ConcreteProduct的類名,只需要知道具體產品類ConcreteProduct對應的引數即可
    • 通過引入配置檔案,可以在不修改客戶端的情況下修改和增加新的產品類ConcreteProduct, 提高了系統的靈活性
  • 簡單工廠模式缺點:

    • 工廠類Factory集中了所有產品的建立邏輯,如果發生異常,整個系統都會發生故障
    • 簡單工廠模式中增加了系統中類的個數,增加了系統的複雜度和理解難度
    • 簡單工廠模式中如果需要新增新的產品需要修改工廠邏輯,違背了開閉原則,不利於系統的擴充套件和維護
    • 簡單工廠模式使用了靜態方法,無法形成基於繼承的等級結構
  • 簡單工廠模式的使用場景:

    • 工廠類中負責建立的物件比較少時
    • 客戶端只需要知道傳入工廠類的引數,不關心建立物件的引數

簡單工廠類的實現方式

直接傳入判斷引數key

  • Factory:

    public class Factory {
      public static Product produce(String concreteProductType) {
          switch (concreteProductType) {
              case "A" :
                  return new ConcreteProductA();
                  break;
              case "B" :
                  return new ConcreteProductB();
                  break;
              default :
                  throw new Exception("沒有對應的產品型別");
                  break;
          }
      }
    }
  • 問題:

    • 如果新增產品類,需要在工廠類中新增case
    • 違背了開閉原則,這種方法是不建議使用的

利用反射

  • Factory:
public Class Factory {
    public static Product produce(String concreteProductClassPathName) throw Exception {
        try {
            Product product = (Product)Class.forName(concreteProductClassPathName).newInstance();
            return product;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        throw new Exception("沒有對應的產品");
    }
}
  • 問題:

    • 如果新增產品類,需要傳入具體產品類的類路徑名稱
    • 可以通過配置檔案進行優化,將具體產品類的類路徑名稱配置在properties檔案中,通過載入配置檔案將類路徑名稱傳入工廠類的方法中
    • 這樣新增產品類,只需要修改配置檔案即可

反射和配置檔案結合

  • product.properties:

    A=com.oxford.factory.simple.ConcreteProductA
    B= com.oxford.factory.simple.ConcreteProductB
  • PropertyReader: 增加一個配置檔案讀取類,將配置檔案的資訊讀取到Map

    public Class PropertyReader {
      public static Map<String, String> property = new HashMap<>();
      public Map<String, String> readProperty(String fileName) {
          Properties properties = new Properties();
          InputStream input = getClass.getResourceAsStream(fileName); 
          try {
              pro.load(input);
              Iterator<String> iterator = pro.StringPropertyNames().iterator();
              while (iterator.hasNext()) {
                  String key = iterator.next();
                  String value = properties.getProperty(key);
                  map.put(key, value);
              }
              input.close();
          } catch (IOException e) {
              e.printStacTrace();
          }
          return map;
      }
    }
  • Factory:

    public Class Factory {
      public static Product produce(String concreteProductType) throws Exception {
          PropertyReader reader = new PropertyReder();
          Map<String, String> property = reader.readProperty("property.properties");
          try {
              Product product = (Product)Class.forName(property.get(concreteProductType)).newInstance();
              return product; 
          } catch (InstantiationException e) {
              e.printStackTrace();
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }
          throw new Exception("沒有對應的產品");
      }
    }
  • 問題:

    • 每次呼叫的方法時,都要解析配置檔案,增加系統開銷
    • 可以在檔案讀取類在程式啟動時就載入,就可以不用在每次呼叫時解析配置檔案了

簡單工廠模式總結

  • 工廠類是整個簡單工廠模式的關鍵:

    • 工廠類中包含必要的判斷邏輯,根據給定的引數來決定建立哪一個具體的產品類
    • 通過使用工廠類,客戶端只需要消費具體產品即可,而不用關注具體產品物件的建立過程
    • 通過使用簡單工廠模式明確了各個類的職責,有利於整個軟體體系結構的優化
  • 工廠類中集中了所有具體物件的建立邏輯,違背了高內聚的責任分配原則.這樣工廠類中建立的類只能是事先考慮到的,如果需要新增新的類,則需要修改工廠類的邏輯,這違背了開閉原則
  • 當系統中的具體產品類不斷增多時,就會出現要求工廠類根據不同的條件建立不同的例項的需求.這種對條件的判斷和對具體產品型別的判斷交錯在一起,不利於對系統的擴充套件和維護.這樣的問題可以通過使用工廠方法模式進行優化

工廠方法模式

  • 工廠方法模式Factory Method Pattern:

    • 定義一個建立物件的介面,通過實現這個介面的類來決定例項化具體的類
    • 工廠方法模式讓具體的類的例項化延遲到子類中進行
  • 工廠方法模式的角色:

    • 工廠類Factory:

      • 工廠方法介面,通常返回一個抽象產品型別Product的例項物件
      • 這個類是工廠方法模式的核心,與客戶端程式無關. 任何在模式中建立的具體產品都需要實現這個介面
    • 工廠實現類ConcreteFactory:

      • 工廠類介面實現,覆寫工廠類Factory定義的工廠方法,返回具體產品類ConcreteProduct的抽象產品型別Product型別的例項
      • 工廠實現類ConcreteFactory中包含與客戶端密切相關的邏輯,並且被客戶端呼叫來建立具體的產品例項
    • 抽象產品類Product:

      • 工廠方法模式建立的具體產品類的父類,定義類具體產品中共有的方法
    • 具體產品類ConcreteProduct:

      • 具體產品實現類,實現了抽象產品類Product中的方法
      • 工廠模式建立的每一個物件都是具體產品類ConcreteProduct的一個例項
        在這裡插入圖片描述
  • 工廠方法模式程式碼實現
  • 工廠方法模式優點:

    • 在工廠方法模式中,通過工廠方法來建立客戶端需要的產品ConcreteProduct, 使用者只需要關心需要具體產品ConcreteProduct對應的工廠實現ConcreteFactory. 不需要關心具體產品ConcreteProduct的建立細節和具體產品類ConcreteProduct的名稱
    • 基於工廠類Factory和抽象產品類Product的多型性設計是工廠方法模式的關鍵. 這樣工廠類Factory可以自主確定需要建立何種產品ConcreteProduct的物件,並且建立具體產品ConcreteProduct物件的具體實現封裝在具體工廠ConcreteFactory的內部. 具體工廠類ConcreteFactory都具有同一父類介面Factory, 因此工廠方法模式又稱為多型工廠模式
    • 工廠方法模式完全符合開閉原則,有利於系統的擴充套件和維護. 工廠方法模式在系統中新增新產品時,只需要新增一個具體工廠類ConcreteFactory和具體產品類ConcreteProduct即可
  • 工廠方法模式缺點:

    • 工廠模式在系統中新增新產品時,需要新增具體產品類ConcreteProduct和具體工廠類ConcreteFactory, 系統中類的個數成對增加,一定程度上增加了系統複雜度以及系統編譯執行的開銷
  • 工廠方法模式的使用場景:

    • 一個類不需要知道所需要的物件的類: 工廠方法模式中,客戶端不知道具體產品類的類名,只知道具體的產品物件由哪一個具體工廠實現來建立. 這時,客戶端需要知道建立具體產品的具體工廠實現類
    • 一個類通過子類來指定建立哪一個物件: 工廠方法模式中,工廠類中只需要一個建立產品的的介面,由子類來確定具體要建立的物件,通過利用多型和里氏代換原則,可以在程式執行時,通過子類物件覆蓋父類物件,從而使得系統得以擴充套件
    • 通過將建立具體產品的任務交由工廠類的具體工廠實現來完成,客戶端不需要關心具體產品類的建立, 需要的時候動態指定產品的具體工廠實現即可. 可以將具體工廠類的類名儲存在配置檔案或者資料庫中
    • 工廠方法模式的使用場景示例:

      • 日誌記錄器: 日誌可以記錄到本地磁碟,系統事件,遠端伺服器等等,使用者可以選擇日誌記錄的位置
      • 資料庫訪問: 當使用者不知道最後系統採用哪一類資料庫時,以及資料庫可能會發生變化時
      • 伺服器框架設計: 設計一個連線伺服器的框架時,可能會用到三個協議POP3, IMAP, HTTP時,可以將三個協議看作是具體產品類,使用工廠方法模式實現

工廠方法模式總結

  • 工廠方法模式是簡單工廠模式的抽象和擴充,通過多型,工廠方法模式保持了簡單工廠模式的優點,改善了簡單工廠模式的缺點
  • 工廠方法模式中,核心的工廠類僅僅給出具體工廠實現必須實現的介面,不再負責具體產品的建立,具體產品的建立交由具體的工廠實現完成.這樣使得系統可以在不修改核心的工廠類時進行具體產品實現的擴充套件
  • 優點:

    • 客戶端想要建立物件,只需要知道具體工廠實現即可
    • 系統的擴充套件性高,如果新增產品,只需要一個具體工廠實現類和具體產品類即可,符合開閉原則
    • 對客戶端隱藏了具體實現,客戶端只需要關心具體的工廠實現即可
  • 缺點:

    • 每次增加一個產品,都需要增加一個具體工廠實現類和具體產品類,這樣使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,也增加了系統具體類的依賴,同時類的增加也增加了編譯和執行時的系統開銷

抽象工廠模式

  • 抽象工廠模式Abstract Factory Pattern:

    • 提供介面或者抽象類用於建立一組相關或者相互依賴的具體產品物件,不需要指定具體的類
  • 抽象工廠模式的基本思想:

    • 工廠方法模式通過引入工廠等級結構,解決了簡單工廠模式中工廠類的職責過大的問題.但是由於工廠方法模式中每個工廠只生產一類產品,這樣可能會導致存在大量的工廠類的問題,這樣會增加系統的開銷
    • 可以將一些相關的產品組成一個產品族,由同一個工廠來統一生產

      • 產品族: 位於不同產品等級結構中功能相關聯的產品組成的家族
    • 抽象工廠模式與工廠方法模式區別:

      • 抽象工廠模式:

        • 抽象工廠模式是針對多個產品的等級結構
        • 抽象工廠模式的具體產品實現或者繼承於不同的介面或者抽象類
      • 工廠方法模式:

        • 工廠方法模式是針對一個產品的等級結構
        • 工廠方法模式的具體產品實現或者繼承於同一個介面或者抽象類
  • 抽象工廠模式的角色:

    • 抽象工廠類AbstractFactory: 抽象工廠模式的核心,與應用的業務邏輯無關. 通常使用介面或者抽象類實現,所有具體工廠類ConcreteFactory必須實現介面或者抽象類
    • 具體工廠類ConcreteFactory: 實現工廠定義的方法,包含建立具體產品例項的業務邏輯
    • 抽象產品AbstractProduct: 定義一類產品物件的介面或者抽象類,這個類是工廠方法模式建立的物件的父類
    • 具體產品ConcreteProduct: 實現業務邏輯的具體的產品,抽象工廠中建立的每一個產品物件都是一個具體產品的例項
      在這裡插入圖片描述
  • 抽象工廠模式程式碼實現
  • 抽象工廠模式優點:

    • 抽象工廠模式分隔了具體類的生成,客戶端不需要知道具體建立的類
    • 當一個產品族中的物件設計成一起工作時,能夠保證客戶端只使用同一個產品族的物件
  • 抽象工廠模式缺點:

    • 如果新增新的產品物件時,難以對產品等級結構進行擴充套件
  • 抽象工廠模式的使用場景:

    • 一個系統中不依賴於產品類的具體例項的建立,組合以及表達的細節
    • 系統中有多個產品族,並且每次只使用其中一種產品族
    • 同一個產品族的產品會在一起使用
    • 系統中提供一個產品類的庫,客戶端不依賴具體產品的實現,所有產品以同樣的介面出現
    • 系統結構穩定,不會頻繁增加產品族
  • 抽象工廠模式問題: 開閉原則的傾斜性

    • 抽象工廠模式中開閉原則的傾斜性是指在抽象工廠模式中,增加新的產品方便,但是增加新的產品族很麻煩
    • 開閉原則要求系統對修改關閉,對擴充套件開放.對於多個產品族和多個產品等級結構的系統的功能擴充套件增強包括:

      • 增加產品: 對於增加新的產品,只需要增加一個對應的具體工廠即可,不需要修改已有的程式碼
      • 增加產品族: 對於增加新的產品族的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品族產品的方法,這是違背了開閉原則的

        • 因為抽象工廠模式存在開閉原則的傾斜性,因此要求在系統設計之初就要考慮整個系統的所有產品族,不會在設計完成之後再增加新的產品族,也不會刪除已有的產品族.否則會導致系統有大量的修改,難以維護

抽象工廠模式總結

  • 抽象工廠模式是工廠方法模式的進一步擴充,提供了更為強大的工廠類用於系統的擴充套件
  • 抽象工廠模式分隔了具體類的生成,客戶端無需瞭解產品的建立過程,這樣使得很容易切換具體工廠.因為所有的具體工廠都實現了抽象工廠中定義的公共介面,因此只需要改變具體工廠的例項,就可以改變整個系統的行為
  • 當一個產品族中的多個產品物件一起工作時,可以保證客戶端始終只使用同一個產品族中的物件
  • 增加新的產品很方便,無需修改已有的系統,符合開閉原則
  • 但是增加系統新的產品族的產品等級結構很麻煩,需要對原有的系統大量修改,甚至需要修改抽象層程式碼,違背了開閉原則

相關文章