Abstract Factory Pattern(抽象工廠模式)
是什麼?
Abstract Factory Pattern 提供一個介面來建立一系列相關或相依的物件,而不需要指定它們的具體類別。每個具體工廠負責產出一整套風格一致的產品。
ℹ️GoF 分類
Abstract Factory 屬於建立型模式(Creational Pattern),重點在於建立一系列相關物件,確保產品之間的一致性。
什麼時候用?
用以下 if/then 條件判斷:
- 如果你的系統需要支援多種「產品家族」(Product Family,指一組彼此相關且必須搭配使用的產品,例如 Windows 的 Button + TextBox)→ 用 Abstract Factory
- 如果混用不同家族的產品會導致錯誤(例如 MacButton 搭配 WindowsTextBox)→ 用 Abstract Factory 保證一致性
- 如果你只需要建立單一種類的產品(沒有家族概念)→ Factory Method 就夠了
什麼時候不該用?
⚠️過度設計警告
如果你的產品不存在「家族」概念(產品之間沒有相依性),用 Abstract Factory 只會增加不必要的介面和類別。單一產品的建立用 Factory Method 就夠了,不需要升級到 Abstract Factory。
執行流程
定義產品介面
為每種產品定義各自的介面(如 IButton、ITextBox)
定義抽象工廠
宣告建立每種產品的方法(CreateButton、CreateTextBox)
實作具體產品
每個家族各自實作所有產品介面
實作具體工廠
每個具體工廠產出同一家族的所有產品
客戶端透過抽象工廠操作
切換工廠就切換整套產品,客戶端程式碼不用改
流程解讀:Abstract Factory 的核心是「產品家族的一致性保證」。抽象工廠定義了建立每種產品的方法,每個具體工廠負責產出同一家族的全套產品。Client 只透過抽象工廠和產品介面操作,切換家族只需換具體工廠。結構上的限制確保你不可能拿到 WindowsButton + MacTextBox 的混搭組合。
程式碼範例
C# 版本
// 1. 產品介面
public interface IButton
{
string Render();
}
public interface ITextBox
{
string Render();
}
// 2. Windows 家族
public class WindowsButton : IButton
{
public string Render() => "[Windows Button] 扁平化風格按鈕";
}
public class WindowsTextBox : ITextBox
{
public string Render() => "[Windows TextBox] 方角輸入框";
}
// 3. macOS 家族
public class MacButton : IButton
{
public string Render() => "[Mac Button] 圓角漸層按鈕";
}
public class MacTextBox : ITextBox
{
public string Render() => "[Mac TextBox] 圓角輸入框";
}
// 4. 抽象工廠
public interface IUIFactory
{
IButton CreateButton();
ITextBox CreateTextBox();
}
// 5. 具體工廠
public class WindowsUIFactory : IUIFactory
{
public IButton CreateButton() => new WindowsButton();
public ITextBox CreateTextBox() => new WindowsTextBox();
}
public class MacUIFactory : IUIFactory
{
public IButton CreateButton() => new MacButton();
public ITextBox CreateTextBox() => new MacTextBox();
}
// 6. 使用
IUIFactory factory = new MacUIFactory();
var button = factory.CreateButton();
var textBox = factory.CreateTextBox();
Console.WriteLine(button.Render()); // [Mac Button] 圓角漸層按鈕
Console.WriteLine(textBox.Render()); // [Mac TextBox] 圓角輸入框TypeScript 版本
// 1. 產品介面
interface Button {
render(): string;
}
interface TextBox {
render(): string;
}
// 2. Windows 家族
class WindowsButton implements Button {
render(): string { return "[Windows Button] 扁平化風格按鈕"; }
}
class WindowsTextBox implements TextBox {
render(): string { return "[Windows TextBox] 方角輸入框"; }
}
// 3. macOS 家族
class MacButton implements Button {
render(): string { return "[Mac Button] 圓角漸層按鈕"; }
}
class MacTextBox implements TextBox {
render(): string { return "[Mac TextBox] 圓角輸入框"; }
}
// 4. 抽象工廠
interface UIFactory {
createButton(): Button;
createTextBox(): TextBox;
}
// 5. 具體工廠
class WindowsUIFactory implements UIFactory {
createButton(): Button { return new WindowsButton(); }
createTextBox(): TextBox { return new WindowsTextBox(); }
}
class MacUIFactory implements UIFactory {
createButton(): Button { return new MacButton(); }
createTextBox(): TextBox { return new MacTextBox(); }
}
// 6. 使用
const factory: UIFactory = new MacUIFactory();
console.log(factory.createButton().render()); // [Mac Button] 圓角漸層按鈕
console.log(factory.createTextBox().render()); // [Mac TextBox] 圓角輸入框Python 版本
from abc import ABC, abstractmethod
# 1. 產品介面
class Button(ABC):
@abstractmethod
def render(self) -> str:
pass
class TextBox(ABC):
@abstractmethod
def render(self) -> str:
pass
# 2. Windows 家族
class WindowsButton(Button):
def render(self) -> str:
return "[Windows Button] 扁平化風格按鈕"
class WindowsTextBox(TextBox):
def render(self) -> str:
return "[Windows TextBox] 方角輸入框"
# 3. macOS 家族
class MacButton(Button):
def render(self) -> str:
return "[Mac Button] 圓角漸層按鈕"
class MacTextBox(TextBox):
def render(self) -> str:
return "[Mac TextBox] 圓角輸入框"
# 4. 抽象工廠
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_text_box(self) -> TextBox:
pass
# 5. 具體工廠
class WindowsUIFactory(UIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_text_box(self) -> TextBox:
return WindowsTextBox()
class MacUIFactory(UIFactory):
def create_button(self) -> Button:
return MacButton()
def create_text_box(self) -> TextBox:
return MacTextBox()
# 6. 使用
factory: UIFactory = MacUIFactory()
print(factory.create_button().render()) # [Mac Button] 圓角漸層按鈕
print(factory.create_text_box().render()) # [Mac TextBox] 圓角輸入框Java 版本
// 1. 產品介面
public interface Button { String render(); }
public interface TextBox { String render(); }
// 2. Windows 家族
public class WindowsButton implements Button {
public String render() { return "[Windows Button] 扁平化風格按鈕"; }
}
public class WindowsTextBox implements TextBox {
public String render() { return "[Windows TextBox] 方角輸入框"; }
}
// 3. macOS 家族
public class MacButton implements Button {
public String render() { return "[Mac Button] 圓角漸層按鈕"; }
}
public class MacTextBox implements TextBox {
public String render() { return "[Mac TextBox] 圓角輸入框"; }
}
// 4. 抽象工廠
public interface UIFactory {
Button createButton();
TextBox createTextBox();
}
// 5. 具體工廠
public class WindowsUIFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public TextBox createTextBox() { return new WindowsTextBox(); }
}
public class MacUIFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public TextBox createTextBox() { return new MacTextBox(); }
}
// 6. 使用
UIFactory factory = new MacUIFactory();
System.out.println(factory.createButton().render());
System.out.println(factory.createTextBox().render());結構圖
結構解讀:Client 只依賴 UIFactory 介面和產品介面(Button、TextBox),不知道具體實作。每個 ConcreteFactory(WindowsUIFactory、MacUIFactory)負責產出同一家族的所有產品。切換平台只需換工廠實例,Client 程式碼完全不用改。新增家族(如 LinuxUIFactory)只需新增工廠和對應產品,但新增產品種類(如 Checkbox)需要修改所有工廠介面。
實戰補充
💡資深開發者經驗
跨平台 UI:React Native、Flutter 底層都運用了類似 Abstract Factory 的概念 — 同一套 API,在不同平台渲染不同的原生元件。
資料庫 Provider 切換:ADO.NET 的 DbProviderFactory 就是 Abstract Factory。切換 SqlClientFactory 和 NpgsqlFactory,就能在 SQL Server 和 PostgreSQL 之間切換,業務程式碼完全不用改。
與 Factory Method 的差別:Factory Method 建立「一種」產品,Abstract Factory 建立「一整套」產品。如果你只需要建立單一物件,Factory Method 就夠了。
理解測驗
🤔 Abstract Factory 和 Factory Method 最大的差異是什麼?
🤔 為什麼 Abstract Factory 能確保產品之間的一致性?
🤔 以下哪個場景最適合用 Abstract Factory?
面試常見問題
Q: Abstract Factory 最大的缺點是什麼?如何緩解?
A: 新增產品種類時,所有工廠介面和具體工廠都要修改。例如原本只有 Button 和 TextBox,新增 Checkbox 時,IUIFactory 要加方法、WindowsUIFactory 和 MacUIFactory 都要實作。緩解方式:(1) 用泛型 Factory Method 如 Create<T>();(2) 改用 Prototype Registry,每個家族用一組原型物件。
Q: 實務中 Abstract Factory 最常見的應用場景是什麼?
A: 跨平台 UI 框架和資料庫 Provider 切換。ADO.NET 的 DbProviderFactory 就是 Abstract Factory — SqlClientFactory 產出 SqlConnection + SqlCommand,NpgsqlFactory 產出 NpgsqlConnection + NpgsqlCommand,業務程式碼透過抽象介面操作,切換資料庫只需換 Factory。
相關模式
| 模式 | 關係 | |------|------| | Factory Method | Abstract Factory 內部通常包含多個 Factory Method,各負責建立一種產品 | | Singleton | Abstract Factory 本身常實作為 Singleton,確保全域只有一個工廠入口 | | Prototype | 可以替代 Abstract Factory — 用原型物件的 Clone 來建立產品,不需要每個家族都寫一個工廠類別 | | Builder | 都是建立型模式,但 Builder 關注「建構步驟」,Abstract Factory 關注「產品家族一致性」 |
重點整理
💡一句話記住
Abstract Factory = IKEA 風格套組:選一個風格,整套家具自動搭配。 口訣:「換工廠換全套,混搭免擔心」
| 概念 | 說明 | |------|------| | AbstractFactory(抽象工廠) | 定義建立每種產品的方法介面 | | ConcreteFactory(具體工廠) | 產出同一家族的所有產品 | | AbstractProduct(抽象產品) | 每種產品的介面 | | ConcreteProduct(具體產品) | 特定家族的產品實作 | | 核心好處 | 保證產品家族一致性 + 切換整套產品只需換工廠 | | 代價 | 新增產品種類時,所有工廠都要改(介面擴增) |