C++ 設計模式 - 抽象工廠模式 Abstract Factory Pattern

在學習設計模式時,你可能會遇到一個問題:「為什麼有這麼多工廠模式?他們到底在解決什麼問題?」工廠方法模式(Factory Method Pattern)提供了一個方法來建立物件,這個方法可以在子類中覆蓋,以便建立不同類型的物件。而抽象工廠模式(Abstract Factory Pattern)則進一步擴展,允許你建立一系列相關的物件,這對於組織和管理大型系統非常有幫助。今天我們將透過一個GUI(圖形使用者界面)的例子來解釋這個概念。

什麼是抽象工廠模式?

抽象工廠模式的核心思想是提供一個接口,讓客戶端程式能夠產生一系列相關的或相互依賴的物件,而不必指定具體的類型。這種模式特別適合用於需要產生不同風格或主題的GUI元件的場景。假設我們正在開發一個跨平台的應用程式,該應用可以執行在Windows、macOS、Linux等多個平台上,並且每個平台都有自己獨特的GUI風格。在這種情況下使用抽象工廠模式可以讓我們輕鬆地為不同平台產生相應風格的GUI元件。

在解釋具體範例之前,讓我們先來看一下抽象工廠模式中的幾個核心角色:
抽象工廠(Abstract Factory):定義建立一系列相關物件的方法接口。這些方法通常與產品家族有關,例如在GUI應用中,這些方法可能包括建立按鈕(Button)、文字框(TextBox)等。
具體工廠(Concrete Factory):實現抽象工廠的接口,負責產生具體的產品。每一個具體工廠對應一個具體的產品家族。例如,Windows 工廠負責產生 Windows 風格的按鈕和文字框,而 macOS 工廠則產生 Mac 風格的物件。
抽象產品(Abstract Product):定義產品的接口。這些接口將由具體產品來實現。例如,Button 和 TextBox 分別是兩個不同的抽象產品接口。
具體產品(Concrete Product):實現抽象產品接口的具體類。每一個具體產品都由相應的具體工廠來建立。

抽象工廠模式在GUI元件的應用

假設我們要在需要 Windows 與 Mac 下建立不同平台的按鈕(Button)和文字框(TextBox)。

首先我們需要定義這些元件的抽象產品介面,

1
2
3
4
5
6
7
8
9
10
11
12
// 抽象產品 Abstract Product
class Button {
public:
virtual void render() = 0;
virtual ~Button() = default;
};

class TextBox {
public:
virtual void render() = 0;
virtual ~TextBox() = default;
};

接著我們定義一個抽象工廠介面來產生這些GUI元件,

1
2
3
4
5
6
7
// 抽象工廠 Abstract Factory
class GUIFactory {
public:
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<TextBox> createTextBox() = 0;
virtual ~GUIFactory() = default;
};

然後我們定義具體產品,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 具體產品 Concrete Product
class WindowsButton : public Button {
public:
void render() override {
std::cout << "Render a Windows style button." << std::endl;
}
};

class WindowsTextBox : public TextBox {
public:
void render() override {
std::cout << "Render a Windows style text box." << std::endl;
}
};

class MacButton : public Button {
public:
void render() override {
std::cout << "Render a Mac style button." << std::endl;
}
};

class MacTextBox : public TextBox {
public:
void render() override {
std::cout << "Render a Mac style text box." << std::endl;
}
};

每個平台會有自己的具體工廠來產生相應風格的GUI元件,我們來定義具體的工廠來產生這些元件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 具體工廠 Concrete Factory
class WindowsGUIFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<WindowsTextBox>();
}
};

class MacGUIFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<MacButton>();
}
std::unique_ptr<TextBox> createTextBox() override {
return std::make_unique<MacTextBox>();
}
};

現在我們可以寫一些客戶端程式碼來產生和使用這些GUI元件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void clientCode(GUIFactory& factory) {
auto button = factory.createButton();
auto textBox = factory.createTextBox();

button->render();
textBox->render();
}

int main() {
WindowsGUIFactory windowsFactory;
MacGUIFactory macFactory;

std::cout << "Using Windows GUI Factory:\n";
clientCode(windowsFactory);

std::cout << "Using Mac GUI Factory:\n";
clientCode(macFactory);

return 0;
}

在這段程式碼中,我們只需要告訴客戶端(使用者)使用哪個工廠,它就能夠產生適合該平台的GUI元件,而不需要知道具體的類型。這讓程式碼更加靈活以及容易擴展。

抽象工廠模式的優缺點

抽象工廠模式優點就像一個能夠保持風格統一的工具箱,比如說當你需要開發一個跨平台的應用程式時,它讓你能夠輕鬆地在不同平台之間切換,並確保你的界面風格一致。當你需要支持新的平台或者改變應用的整體外觀風格時,只需要新增一個新的工廠類,現有的程式碼幾乎不需要做任何改動。這種擴展性讓你能夠輕鬆應對未來的需求變化,而不需要擔心系統的核心結構被打亂。

然而抽象工廠模式也不是沒有缺點的,當你匯入抽象工廠模式後,你的系統架構會變得複雜,特別是當你的專案規模不大時,這種複雜性可能會讓人感覺程式碼變得繁瑣。就好像你本來只需要一個簡單的工具箱,但卻因為引入了這麼多「多功能」工具而變得過於複雜。另外當你想在現有的工廠中新增一個產品類型時,可能會遇到一些困難。你必須去修改所有相關的工廠類,這樣的改動有時候會違背開放封閉原則。這意味著,每當你有新的需求時,你都得花一些時間去調整原本的程式碼,這可能會讓你覺得有些麻煩。

整體來說,抽象工廠模式就像是一把雙刃劍,它能夠幫助你打造出一個統一、靈活的系統,但同時也會增加系統的複雜性和維護成本。所以在選擇是否使用這個模式時,還是得根據實際的需求來決定,不要「為了模式而模式」。

抽象工廠模式與工廠方法模式的區別

抽象工廠模式是專注於建立一系列相關聯的產品。適合需要建立整套相關物件(例如一整個產品家族)的場合。

工廠方法模式專注於為一個產品家族中的單一產品建立對應的工廠。也就是說如果你只需要產生某一類型的物件(例如按鈕、文字框等),工廠方法模式是理想的選擇。每一個工廠方法只關心如何產生單一類型的產品,而不是整個產品家族。

例如在 GUI 開發中,假設你只需要根據不同的平台(Windows 或 Mac)產生特定風格的按鈕,那麼你可以使用工廠方法模式來為每個平台建立對應的按鈕工廠。這樣當你需要一個按鈕時,你只需呼叫對應的工廠方法來取得相應平台的按鈕。

總結

抽象工廠模式讓我們能夠優雅地管理和組織大型系統中的物件建立過程。在GUI開發中這種模式可以讓我們輕鬆地產生不同平台的元件,還有很多應用場合,例如:特斯拉工廠不僅生產不同的車型,還能生產不同的零組件,比如「標準版」或「高性能版」的輪胎和引擎,或者假設你正在開發一個需要支援多個資料庫(如MySQL、SQL Server、Oracle)的應用程式。每個資料庫有自己特定的連線方式、查詢語法和資料處理方式等等例子,下次當你面對需要建立一系列相關物件的場景時,不妨考慮使用抽象工廠模式。

其它相關文章推薦
如果你想學習設計模式相關技術,可以參考看看下面的文章,
簡單工廠模式 Simple Factory Pattern
工廠方法模式 Factory Method Pattern