工廠方法模式

<image>

工廠方法模式（factory method pattern），是一種實現了工廠概念的面向對象設計模式。就像其他創建型模式一樣，它也是處理在不指定對象具體類型的情況下創建對象的問題。

## 概述

對象創建中的有些過程包括決定創建哪個對象、管理對象的生命周期，以及管理特定對象的建立和銷毀的概念。工廠方法模式的實質是「定義一個創建對象的接口，但讓實現這個接口的類來決定實例化哪個類。工廠方法讓類的實例化推遲到子類中進行。」

創建一個對象常常需要複雜的過程，所以不適合包含在一個複合對象中。創建對象可能會導致大量的重複代碼，可能會需要複合對象訪問不到的資訊，也可能提供不了足夠級別的抽象，還可能並不是複合對象概念的一部分。工廠方法模式通過定義一個單獨的創建對象的方法來解決這些問題。由子類實現這個方法來創建具體類型的對象。

## 結構

<image>

在上面的UML類圖中，`Creator`類要求`Product`對象，但不直接的實例化`Product1`類。`Creator`轉而提及一個獨立的`factoryMethod()`來創建一個產品對象，這使得`Creator`獨立於被實例化的確切的具體類。`Creator`的子類可以精製（redefine）出哪個類要被實例化。在這個例子中，`Creator1`子類通過實例化`Product1`類來實現抽象的`factoryMethod()`。

## 使用

工廠方法模式，適用於基於接口編程與實現依賴反轉原則。

下列情況可以考慮使用工廠方法模式：

- 創建對象需要大量重複的代碼。可以把這些代碼寫在工廠基類中。
- 創建對象需要訪問某些資訊，而這些資訊不應該包含在複合類中。
- 創建對象的生命周期必須集中管理，以保證在整個程序中具有一致的行為。對象創建時會有很多參數來決定如何創建出這個對象。
- 創建對象可能是一個pool里的，不是每次都憑空創建一個新的。而pool的大小等參數可以用另外的邏輯去控制。比如連接池對象，線程池對象
- 業務對象的代碼作者希望隱藏對象的真實類型，而構造函數一定要真實的類名才能用
- 簡化一些常規的創建過程。根據配置去創建一個對象也很複雜；但可能95%的情況只創建某個特定類型的對象。這時可以弄個函數直接省略那些配置過程。如Java的線程池的相關創建api（如Executors.newFixedThreadPool等）
- 創建一個對象有複雜的依賴關係，比如Foo對象的創建依賴A，A又依賴B，B又依賴C……。於是創建過程是一組對象的的創建和注入。
- 知道怎麼創建一個對象，但是無法把控創建的時機。需要把「如何創建」的代碼塞給「負責決定什麼時候創建」的代碼。後者在適當的時機，回調執行創建對象的函數。在支持用函數作為一等公民傳參的語言，比如js，go等，直接用創建函數就行了。對於java需要搞個XXXXFactory的類去傳。
- 構造函數裡不要拋出異常。

工廠方法模式常見於工具包和框架中，在這些庫中可能需要創建客戶端代碼實現的具體類型的對象。平行的類層次結構中，常常需要一個層次結構中的對象能夠根據需要創建另一個層次結構中的對象。工廠方法模式可以用於測試驅動開發，從而允許將類放在測試中。

舉例來說，`Foo`這個類創建了一個`Dangerous`對象，但是`Dangerous`對象不能放在自動的單元測試中（可能它需要訪問產品資料庫，而這個資料庫不是隨時能夠訪問到的）。所以，就可以把`Dangerous`對象的創建交由`Foo`類的一個方法（虛函數）`createDangerous`完成。為了測試，再創建一個`Foo`的一個子類`TestFoo`，重寫`createDangerous`方法，在方法中創建並返回一個`FakeDangerous`（`Dangerous`的子類），而這是一個模擬對象。這樣，單元測試就可以使用`TestFoo`來測試`Foo`的功能，從而避免了使用`Dangerous`對象帶來的副作用。

## 代碼舉例

### Java

下面的迷路園遊戲的Java例子，類似於《設計模式》中的例子，這裡針對迷路園生成演算法而簡化了對房間的四面、門和牆的處置，即一個房間與毗鄰的另一個房間，如果二者連通則其間有開著的一個門，如果二者不連通則其間有一個牆；永遠開著的門通常就表現為房間的這一面沒有牆，如果要將門具象表現出來則附含著其所處在的牆。

<image>

抽象類`MazeGame`連通多個`Room`對象，但是將創建`Room`對象的責任委託給了作為具體類的它的子類。`MazeGame()`構造器是增加一些公用邏輯的模板方法，其中涉及了作為虛方法的`makeRoom()`工廠方法，它在`MazeGame`的子類中被覆寫，從而可以使用上各種不同的房間：

```java
public abstract class Room {
    abstract void connect(Room room);
}

public class MagicRoom extends Room {
    public void connect(Room room) {}
}

public class OrdinaryRoom extends Room {
    public void connect(Room room) {}
}

public abstract class MazeGame {
    private final List<Room> rooms = new ArrayList<>();
    abstract protected Room makeRoom();
    public MazeGame() {
        Room room1 = makeRoom();
        Room room2 = makeRoom();
        room1.connect(room2);
        rooms.add(room1);
        rooms.add(room2);
    }
}

public class MagicMazeGame extends MazeGame {
    @Override
    protected MagicRoom makeRoom() {
        return new MagicRoom();
    }
}

public class OrdinaryMazeGame extends MazeGame {
    @Override
    protected OrdinaryRoom makeRoom() {
        return new OrdinaryRoom();
    }
}

MazeGame ordinaryGame = new OrdinaryMazeGame();
MazeGame magicGame = new MagicMazeGame();
```

### Python

Python的迷路園遊戲例子：

```python
from abc import ABC, abstractmethod

class Room(ABC):
    def __init_subclass__(cls, /, 
            descr=None, params={}, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.descr = descr
        cls.params = params
    def __init__(self, **kwargs):
        self.connected_rooms = []
        params = type(self).params | kwargs
        for key, value in params.items():
            self.__dict__[key] = value
    def __str__(self): 
        return type(self).descr
    @abstractmethod
    def connect(self, other, **kwargs): 
        self.connected_rooms.append(other)

class OrdinaryRoom(Room, 
        descr='Ordinary room', params={'kw1': ''}):
    def connect(self, other):
        super().connect(other, kw0='')

class MagicRoom(Room,
        descr='Magic room', params={'kw1': ''}):
    def connect(self, other):
        super().connect(other, kw0='')

class MazeAbstract(ABC):
    def __init__(self):
        self.rooms = []
        room1 = self.room_factory(kw2='')
        room2 = self.room_factory(kw2='')
        room1.connect(room2)
        self.rooms.append(room1)
        self.rooms.append(room2)
    @abstractmethod
    def room_factory(self): pass
    def play(self):
        print(f"Playing using {self.rooms[0]}")

class Maze(MazeAbstract):
    def __init__(self, room_class=OrdinaryRoom):
        self.room_class = room_class
        super().__init__()
    def room_factory(self, **kwargs):
        return self.room_class(**kwargs)
```

其執行：

```pycon
>>> maze1 = Maze()
>>> maze1.play()
Playing using Ordinary room
>>> maze2 = Maze(MagicRoom)
>>> maze2.play()
Playing using Magic room
```

### C++

下面是基於《設計模式》書中早於C++98實現的C++23實現例子：

```c++
import std;

using std::unique_ptr;

enum class ProductId: char {
    MINE, 
    YOURS
};

// defines the interface of objects the factory method creates.
class Product {
public:
    virtual void print() = 0;
    virtual ~Product() = default;
};

// implements the Product interface.
class ConcreteProductMINE: public Product {
public:
    void print() {
        std::println("this={} print MINE", this);
    }
};

// implements the Product interface.
class ConcreteProductYOURS: public Product {
public:
    void print() {
        std::println("this={} print YOURS", this);
    }
};

// declares the factory method, which returns an object of type Product.
class Creator {
public:
    virtual unique_ptr<Product> create(ProductId id) {
        switch (id) {
            case ProductId::MINE:
                return std::make_unique<ConcreteProductMINE>();
            case ProductId::YOURS:
                return std::make_unique<ConcreteProductYOURS>();
            // repeat for remaining products
            default:
                return nullptr;
        }
    }

    virtual ~Creator() = default;
};

int main(int argc, char* argv[]) {
    unique_ptr<Creator> creator = std::make_unique<Creator>();
    unique_ptr<Product> product = creator->create(ProductId::MINE);
    product->print();

    product = creator->create(ProductId::YOURS);
    product->print();
}
```

程序輸出：

```output
this=0x6e5e90 print MINE
this=0x6e62c0 print YOURS
```

### C#

下面是C#例子：

```csharp
public interface IProduct {
    string GetName();
    bool SetPrice(double price);
}

public class Phone : IProduct {
    private double _price;

    public string GetName() {
        return "Apple TouchPad";
    }

    public bool SetPrice(double price) {
        _price = price;
        return true;
    }
}

/* Almost same as Factory, just an additional exposure to do something with the created method */
public abstract class ProductAbstractFactory {
    protected abstract IProduct MakeProduct();

    public IProduct GetObject() // Implementation of Factory Method. {
        return this.MakeProduct();
    }
}

public class PhoneConcreteFactory : ProductAbstractFactory {
    protected override IProduct MakeProduct() {
        IProduct product = new Phone();
        // Do something with the object after receiving it
        product.SetPrice(20.30);
        return product;
    }
}
```

### 其他用例

- 在ADO.NET中，`IDbCommand.CreateParameter`使用工廠方法連接平行的類層次結構。
- 在Qt中，`QMainWindow::createPopupMenu`是在框架中定義的工廠方法，可以在應用代碼中重寫。
- 在Java中，`javax.xml.parsers`包使用了幾個工廠方法。例如`javax.xml.parsers.DocumentBuilderFactory`和`javax.xml.parsers.SAXParserFactory`。
- 在HTML5 DOM API中，文檔接口包含`createElement()`工廠方法用來創建`HTMLElement`接口的特定元素。