抽象工廠模式

<image>

在軟體工程中，抽象工廠模式（abstract factory pattern），是一種軟體設計模式。抽象工廠模式提供了一種方式，可以將一組具有同一主題的單獨的工廠封裝起來。抽象工廠模式將一組對象的實現細節與他們的一般使用分離開來。

在正常使用中，客戶端程序需要創建抽象工廠的具體實現，然後使用抽象工廠作為接口來創建這一主題的具體對象。客戶端程序不需要知道（或關心）它從這些內部的工廠方法中獲得對象的具體類型，因為客戶端程序僅使用這些對象的通用接口。

## 概述

工廠是創建產品（對象）的地方，其目的是將產品的創建與產品的使用分離。抽象工廠模式的目的，是將若干抽象產品的接口與不同主題產品的具體實現分離開。這樣就能在增加新的具體工廠的時候，不用修改引用抽象工廠的客戶端代碼。

抽象工廠模式的實質是「提供接口，創建一系列相關或獨立的對象，而不指定這些對象的具體類。」使用抽象工廠模式，能夠在具體工廠變化的時候，不用修改使用工廠的客戶端代碼，甚至是在運行時。然而，使用這種模式或者相似的設計模式，可能給編寫代碼帶來不必要的複雜性和額外的工作。正確使用設計模式能夠抵消這樣的「額外工作」。

舉個例子來說，比如一個抽象工廠類叫做`DocumentCreator`（文檔創建器），此類提供創建若干種產品的接口，包括`createLetter()`（創建信件）和`createResume()`（創建簡歷）。其中，`createLetter()`返回一個`Letter`（信件），`createResume()`返回一個`Resume`（簡歷）。

系統中還有一些`DocumentCreator`的具體實現類，包括`FancyDocumentCreator`和`ModernDocumentCreator`。這兩個類對`DocumentCreator`的兩個方法分別有不同的實現，用來創建不同的「信件」和「簡歷」（用`FancyDocumentCreator`的實例可以創建`FancyLetter`和`FancyResume`，用`ModernDocumentCreator`的實例可以創建`ModernLetter`和`ModernResume`）。這些具體的「信件」和「簡歷」類均繼承自抽象類，即`Letter`和`Resume`類。

客戶端需要創建「信件」或「簡歷」時，先要得到一個合適的`DocumentCreator`實例，然後調用它的方法。一個工廠中創建的每個對象都是同一個主題的（「fancy」或者「modern」）。客戶端程序只需要知道得到的對象是「信件」或者「簡歷」，而不需要知道具體的主題，因此客戶端程序從抽象工廠`DocumentCreator`中得到了`Letter`或`Resume`類的引用，而不是具體類的對象引用。

## 結構

<image>

在上面的UML類圖中，`Client`類要求`ProductA`和`ProductB`對象，但它不直接的實例化`ProductA1`和`ProductB1`類。`Client`轉而提及`AbstractFactory`接口來創建對象，這使得`Client`獨立於如何創建對象（哪個具體具體類被實例化）。`Factory1`類通過實例化`ProductA1`和`ProductB1`類來實現`AbstractFactory`接口。

UML序列圖展示了運行時交互：`Client`對象調用`createProductA()`於`Factory1`對象之上，它創建並返回一個`ProductA1`對象。此後，`Client`調用`createProductB()`於`Factory1`之上，它創建並返回一個`ProductB1`對象。

## 使用

在以下情況可以考慮使用抽象工廠模式：

- 一個系統要獨立於它的產品的創建、組合和表示時。
- 一個系統要由多個產品系列中的一個來配置時。
- 需要強調一系列相關的產品對象的設計以便進行聯合使用時。
- 提供一個產品類庫，而只想顯示它們的接口而不是實現時。

抽象工廠模式的優點有：具體產品從客戶代碼中被分離出來，容易改變產品的系列，將一個系列的產品族統一到一起創建。其缺點是：在產品族中擴展新的產品是很困難的，它需要修改抽象工廠的接口。

具體的工廠決定了創建對象的具體類型，而且工廠就是對象實際創建的地方（比如在C++中，用「new」操作符創建對象）。然而，抽象工廠只返回一個指向創建的對象的抽象引用（或指針）。這樣，客戶端程序調用抽象工廠引用的方法，由具體工廠完成對象創建，然後客戶端程序得到的是抽象產品的引用。如此使客戶端代碼與對象的創建分離開來。

因為工廠僅僅返回一個抽象產品的引用（或指針），所以客戶端程序不知道（也不會牽絆於）工廠創建對象的具體類型。然而，工廠知道具體對象的類型；例如，工廠可能從配置文件中讀取某種類型。這時，客戶端沒有必要指定具體類型，因為已經在配置文件中指定了。通常，這意味著：

- 客戶端代碼不知道任何具體類型，也就沒必要引入任何相關的頭文件或類定義。客戶端代碼僅僅處理抽象類型。工廠確實創建了具體類型的對象，但是客戶端代碼僅使用這些對象的抽象接口來訪問它們。
- 如果要增加一個具體類型，只需要修改客戶端代碼使用另一個工廠即可，而且這個修改通常只是一個文件中的一行代碼。不同的工廠創建不同的具體類型的對象，但是和以前一樣返回一個抽象類型的引用（或指針），因此客戶端代碼的其他部分不需要任何改動。這樣比修改客戶端代碼創建新類型的對象簡單多了。如果是後者的話，需要修改代碼中每一個創建這種對象的地方（而且需要注意的是，這些地方都知道對象的具體類型，而且需要引入具體類型的頭文件或類定義）。如果所有的工廠對象都存儲在全局的單例對象中，所有的客戶端代碼到這個單例中訪問需要的工廠，那麼，更換工廠就非常簡單了，僅僅需要更改這個單例對象即可。

## 代碼舉例

假設我們有兩種產品接口Button和Border，每一種產品都支持多種系列，比如Mac系列和Windows系列。這樣每個系列的產品分別是MacButton、WinButton、MacBorder和WinBorder。為了可以在運行時刻創建一個系列的產品族，我們可以為每個系列的產品族建立一個工廠MacFactory和WinFactory。每個工廠都有兩個方法CreateButton和CreateBorder並返回對應的產品，可以將這兩個方法抽象成一個接口AbstractFactory。這樣在運行時刻我們可以選擇創建需要的產品系列。

<image>

在抽象工廠模式中，通常一個工廠能夠創建若干種不同類型的對象，為了簡潔起見，以上類圖僅僅展示了創建一個類型對象的工廠。`GuiFactory`接口中的`createButton`方法返回`Button`類型的對象。返回`Button`的哪種實現依賴於使用`GuiFactory`的哪種實現。

### Java

Java的例子：

```java
//定义接口
public interface Button {}
public interface Border {}

//实现抽象类
public class MacButton implements Button {}
public class MacBorder implements Border {}
public class WinButton implements Button {}
public class WinBorder implements Border {}

//实现工厂
public class MacFactory {
	public static Button createButton() {
	    return new MacButton();
	}
	public static Border createBorder() {
	    return new MacBorder();
	}
}

//客户使用
public class WinFactory {
	public static Button createButton() {
	    return new WinButton();
	}
	public static Border createBorder() {
	    return new WinBorder();
	}
}
```

### Python

Python的例子：

```python
from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def paint(self): pass
    
class Border(ABC):
    @abstractmethod
    def paint(self): pass

class MacButton(Button):
    def paint(self):
        print("It is a MacButton.")

class MacBorder(Border):
    def paint(self):
        print("It is a MacBorder.")

class WinButton(Button):
    def paint(self):
        print("It is a WinButton.")

class WinBorder(Border):
    def paint(self):
        print("It is a WinBorder.")

class GUIFactory(ABC):
    @abstractmethod
    def create_button(self): pass
    @abstractmethod
    def create_border(self): pass

class MacFactory(GUIFactory):
    def create_button(self):
        return MacButton()
    def create_border(self):
        return MacBorder()

class WinFactory(GUIFactory):
    def create_button(self):
        return WinButton()
    def create_border(self):
        return WinBorder()
```

其執行：

```pycon
>>> factory1 = WinFactory()
>>> factory1.create_button().paint()
It is a WinButton.
>>> factory1.create_border().paint()
It is a WinBorder.
>>> factory2 = MacFactory()
>>> factory2.create_button().paint()
It is a MacButton.
>>> factory2.create_border().paint()
It is a MacBorder.
```

### C++

下面是基於《設計模式》書中前C++98實現的C++23實現迷路園遊戲例子：

```c++
import std;

using std::array;
using std::shared_ptr;
using std::unique_ptr;
using std::vector;

enum class Direction: char {
    NORTH, 
    SOUTH, 
    EAST, 
    WEST
};

class MapSite {
public:
    virtual void enter() = 0;
    virtual ~MapSite() = default;
};

class Room: public MapSite {
private:
    int roomNumber;
    shared_ptr<array<MapSite, 4>> sides;
public:
    explicit Room(int n = 0): 
        roomNumber{n} {}

    ~Room() = default;

    Room& setSide(Direction d, MapSite* ms) {
        sides[static_cast<size_t>(d)] = std::move(ms);
        std::println("Room::setSide {} ms", d);
        return *this;
    }

    virtual void enter() override = 0;

    Room(const Room&) = delete;
    Room& operator=(const Room&) = delete;
};

class Wall: public MapSite {
public:
    explicit Wall(int n = 0):
        MapSite(n) {}

    ~Wall() = default;

    void enter() override {
        // ...
    }
};

class Door: public MapSite {
private:
    shared_ptr<Room> room1;
    shared_ptr<Room> room2;
public:
    explicit Door(int n = 0, shared_ptr<Room> r1 = nullptr, shared_ptr<Room> r2 = nullptr): 
        MapSite(n), room1{std::move(r1)}, room2{std::move(r2)} {}

    ~Door() = default;

    void enter() override {
        // ...
    }

    Door(const Door&) = delete;
    Door& operator=(const Door&) = delete;
};

class Maze {
private:
    vector<shared_ptr<Room>> rooms;
public:
    Maze() = default;
    ~Maze() = default;

    Maze& addRoom(shared_ptr<Room> r) {
        std::println("Maze::addRoom {}", reinterpret_cast<void*>(r.get()));
        rooms.push_back(std::move(r));
        return *this;
    }
  
    shared_ptr<Room> roomNo(int n) const {
        for (const Room& r: rooms) {
            // actual lookup logic here...
        }
        return nullptr;
    }
};

class MazeFactory {
public:
    MazeFactory() = default;

    virtual ~MazeFactory() = default;

    [[nodiscard]]
    unique_ptr<Maze> makeMaze() const {
        return std::make_unique<Maze>();
    }

    [[nodiscard]]
    shared_ptr<Wall> makeWall() const {
        return std::make_shared<Wall>();
    }

    [[nodiscard]]
    shared_ptr<Room> makeRoom(int n) const {
        return std::make_shared<Room>(new Room(n));
    }
    
    [[nodiscard]]
    shared_ptr<Door> makeDoor(shared_ptr<Room> r1, shared_ptr<Room> r2) const {
        return std::make_shared<Door>(std::move(r1), std::move(r2));
    }
};

// If createMaze is passed an object as a parameter to use to create rooms, walls, and doors, then you can change the classes of rooms, walls, and doors by passing a different parameter. This is an example of the Abstract Factory (99) pattern.

class MazeGame {
public:
    Maze() = default;
    ~Maze() = default;

    [[nodiscard]]
    unique_ptr<Maze> createMaze(MazeFactory& factory) {
        unique_ptr<Maze> maze = factory.makeMaze();
        shared_ptr<Room> r1 = factory.makeRoom(1);
        shared_ptr<Room> r2 = factory.makeRoom(2);
        shared_ptr<Door> door = factory.makeDoor(r1, r2);
        maze->addRoom(r1)
            .addRoom(r2)
                .setSide(Direction::NORTH, factory.makeWall())
                .setSide(Direction::EAST, door)
                .setSide(Direction::SOUTH, factory.makeWall())
                .setSide(Direction::WEST, factory.makeWall())
                .setSide(Direction::NORTH, factory.makeWall())
                .setSide(Direction::EAST, factory.makeWall())
                .setSide(Direction::SOUTH, factory.makeWall())
                .setSide(Direction::WEST, door);
        return maze;
    }
};

int main(int argc, char* argv[]) {
    MazeGame game;
    unique_ptr<Maze> maze = game.createMaze(MazeFactory());
}
```

程序的輸出為:

```output
Maze::addRoom 0x1317ed0
Maze::addRoom 0x1317ef0
Room::setSide 0 0x1318340
Room::setSide 2 0x1317f10
Room::setSide 1 0x1318360
Room::setSide 3 0x1318380
Room::setSide 0 0x13183a0
Room::setSide 2 0x13183c0
Room::setSide 1 0x13183e0
Room::setSide 3 0x1317f10
```