意圖(Intent)#

提供一個介面,用於建立一系列相關或相互依賴的物件,而不需要指定它們的具體類別。

別名(Also Known As)#

Kit

動機(Motivation)#

假設要開發一套支援多種外觀風格(look-and-feel)的 UI 工具組,例如 Motif 與 Presentation Manager。不同風格對捲軸、視窗、按鈕等 widget 有不同的外觀與行為。如果在應用程式中到處直接實體化特定風格的 widget 類別,日後要切換風格就會非常困難。

解決方法是定義一個抽象的 WidgetFactory 類別,宣告建立各種 widget 的介面。每種外觀風格各有一個對應的 ConcreteFactory(如 MotifWidgetFactory、PMWidgetFactory),負責實體化該風格的 widget。Client 只透過 WidgetFactory 介面取得 widget,完全不知道底層的具體類別。

Abstract Factory 的核心價值在於:讓 client 與具體產品類別完全解耦,同時確保同一家族的產品能一致地搭配使用。

適用場景(Applicability)#

  • 系統需要與產品的建立、組合、表示方式無關
  • 系統需在多個產品家族之間切換配置
  • 一組相關產品物件被設計為一起使用,且需要強制這個約束
  • 想提供產品的類別庫,但只暴露介面,不暴露實作

結構(Structure)#

AbstractFactory 宣告建立各種 AbstractProduct 的介面。每個 ConcreteFactory 實作這些操作,產出對應的 ConcreteProductClient 僅透過抽象介面與工廠和產品互動,不依賴任何具體類別。

classDiagram
    class AbstractFactory {
        <<interface>>
        +CreateProductA()
        +CreateProductB()
    }
    class ConcreteFactory1 {
        +CreateProductA()
        +CreateProductB()
    }
    class ConcreteFactory2 {
        +CreateProductA()
        +CreateProductB()
    }
    class AbstractProductA {
        <<interface>>
    }
    class AbstractProductB {
        <<interface>>
    }
    class ProductA1
    class ProductA2
    class ProductB1
    class ProductB2
    AbstractFactory <|.. ConcreteFactory1
    AbstractFactory <|.. ConcreteFactory2
    AbstractProductA <|.. ProductA1
    AbstractProductA <|.. ProductA2
    AbstractProductB <|.. ProductB1
    AbstractProductB <|.. ProductB2
    ConcreteFactory1 ..> ProductA1 : creates
    ConcreteFactory1 ..> ProductB1 : creates
    ConcreteFactory2 ..> ProductA2 : creates
    ConcreteFactory2 ..> ProductB2 : creates
    Client --> AbstractFactory
    Client --> AbstractProductA
    Client --> AbstractProductB

參與者(Participants)#

參與者範例職責
AbstractFactoryWidgetFactory宣告建立各種抽象產品物件的介面
ConcreteFactoryMotifWidgetFactory、PMWidgetFactory實作建立具體產品物件的操作
AbstractProductWindow、ScrollBar宣告某一類產品的介面
ConcreteProductMotifWindow、MotifScrollBar定義由對應 ConcreteFactory 建立的產品物件;實作 AbstractProduct 介面
Client-僅使用 AbstractFactory 與 AbstractProduct 所宣告的介面

協作方式(Collaborations)#

  • 執行時通常只建立一個 ConcreteFactory 實例。要建立不同的產品物件,client 應使用不同的 ConcreteFactory
  • AbstractFactory 將產品物件的建立延遲到 ConcreteFactory 子類別

優缺點(Consequences)#

優點:

  • 隔離具體類別 —— 工廠封裝了建立產品的職責,client 程式碼中不會出現具體產品的類別名稱
  • 方便切換產品家族 —— ConcreteFactory 在應用程式中只出現一次(實體化的地方),更換工廠就能切換整個產品家族
  • 確保產品一致性 —— 同一工廠產出的產品天生就能搭配使用,不會出現混搭不同家族的問題

缺點:

  • 難以支援新種類的產品 —— AbstractFactory 介面固定了可建立的產品集合。新增產品種類需要修改 AbstractFactory 及其所有子類別

每當需要新增一種產品類型時,必須修改 AbstractFactory 介面及所有 ConcreteFactory,這是此模式最大的擴展性限制。可考慮使用參數化工廠方法來緩解。

實作要點(Implementation)#

  • 工廠通常是 Singleton —— 每個產品家族只需一個 ConcreteFactory 實例,因此常以 Singleton 模式實作
  • 使用 Factory Method 建立產品 —— 最常見的做法是在 AbstractFactory 中為每種產品定義一個 Factory Method,由 ConcreteFactory 覆寫。簡單但每個產品家族都需要一個子類別
  • 使用 Prototype 建立產品 —— 當產品家族很多時,可用原型實例初始化工廠,透過 clone 產生新物件,避免為每個家族建立新的工廠子類別
  • 參數化工廠 —— 用單一 Make 操作搭配參數來指定產品種類,提高彈性但犧牲型別安全性。所有產品以相同的抽象介面回傳,client 無法區分具體型別

如果產品家族數量龐大且變動頻繁,考慮使用 Prototype 方式實作工廠——以原型字典取代大量子類別,新增家族只需註冊新的原型組合。

已知應用(Known Uses)#

  • InterViews 框架使用 “Kit” 後綴命名 AbstractFactory 類別(如 WidgetKit、DialogKit),產生不同風格的 UI 物件
  • ET++ 透過 Abstract Factory 達成跨視窗系統(X Windows、SunView)的可移植性,WindowSystem 抽象基底類別定義建立視窗系統資源的介面

相關模式(Related Patterns)#

  • AbstractFactory 常以 Factory Method 實作,也可以用 Prototype 實作
  • ConcreteFactory 通常是 Singleton