工厂方法

**工廠方法模式**（英語：Factory method pattern）是一種實現了「工廠」概念的物件導向設計模式。就像其他建立型模式一樣，它也是處理在不指定物件具體類型的情況下建立物件的問題。工廠方法模式的實質是「定義一個建立物件的介面，但讓實現這個介面的類來決定實例化哪個類。工廠方法讓類別的實例化推遲到子類中進行。」

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

物件建立中的有些過程包括決定建立哪個物件、管理物件的生命周期，以及管理特定物件的建立和銷毀的概念。

## 工廠

在物件導向程式設計中，**工廠**通常是一個用來建立其他物件的物件。工廠是構造方法的抽象，用來實現不同的分配方案。

工廠物件通常包含一個或多個方法，用來建立這個工廠所能建立的各種類型的物件。這些方法可能接收參數，用來指定物件建立的方式，最後返回建立的物件。

有時，特定類型物件的控制過程比簡單地建立一個物件更複雜。在這種情況下，工廠物件就派上用場了。工廠物件可能會動態地建立產品類的物件，或者從物件池中返回一個物件，或者對所建立的物件進行複雜的組態，或者應用其他的操作。

這些類型的物件很有用。幾個不同的設計模式都應用了工廠的概念，並可以使用在很多語言中。例如，在《設計模式》一書中，像**工廠方法模式**、抽象工廠模式、生成器模式，甚至是單例模式都應用了工廠的概念。

## 代碼舉例

例如，有一個 `Button` 類表示按鈕，另有它的兩個子類 `WinButton` 和 `MacButton` 分別代表 Windows 和 Mac 風格的按鈕，那麼這幾個類和用於建立它們的工廠類在 Java 中可以如下實現（在此省略所有類和方法的可見性設定）：

### 其他舉例

* 在 ADO.NET 中，IDbCommand.CreateParameter （頁面存檔備份，存於網際網路檔案館）使用工廠方法連接平行的類階層。
* 在 Qt，QMainWindow::createPopupMenu 是在框架中定義的工廠方法，可以在應用代碼中重寫。
* 在 Java 中，javax.xml.parsers （頁面存檔備份，存於網際網路檔案館）包使用了幾個工廠方法。例如 javax.xml.parsers.DocumentBuilderFactory 和 javax.xml.parsers.SAXParserFactory。

## 變種

雖然工廠方法模式的背後動機是允許子類選擇建立物件的具體類型，但是使用工廠方法模式也有一些其他的好處，其中很多並不依賴於子類。因此，有時候也會建立不使用多型性建立物件的工廠方法，以得到使用工廠方法的其他好處。

### 工廠「方法」而非工廠「類」

如果拋開設計模式的範疇，「工廠方法」這個詞也可以指作為「工廠」的方法，這個方法的主要目的就是建立物件，而這個方法不一定在單獨的工廠類中。這些方法通常作為靜態方法，定義在方法所實例化的類中。

每個工廠方法都有特定的名稱。在許多物件導向的程式語言中，構造方法必須和它所在的類具有相同的名稱，這樣的話，如果有多種建立物件的方式（多載）就可能導致歧義。工廠方法沒有這種限制，所以可以具有描述性的名稱。舉例來說，根據兩個實數建立一個複數，而這兩個實數表示直角坐標或極坐標，如果使用工廠方法，方法的含義就非常清晰了。當工廠方法起到這種消除歧義的作用時，構造方法常常被設定為私有方法，從而強制客戶端代碼使用工廠方法建立物件。

下面的例子展示了在不同的程式語言中實現複數建立的代碼：

#### Java

#### VB.NET

#### C#

### 簡單工廠

普通的工廠方法模式通常伴隨著物件的具體類型與工廠具體類型的一一對應，客戶端代碼根據需要選擇合適的具體類型工廠使用。然而，這種選擇可能包含複雜的邏輯。這時，可以建立一個單一的工廠類，用以包含這種選擇邏輯，根據參數的不同選擇實現不同的具體物件。這個工廠類不需要由每個具體產品實現一個自己的具體的工廠類，所以可以將工廠方法設定為靜態方法。
而且，工廠方法封裝了物件的建立過程。如果建立過程非常複雜（比如依賴於設定檔或使用者輸入），工廠方法就非常有用了。
比如，一個程式要讀取圖檔。程式支援多種圖像格式，每種格式都有一個對應的 `ImageReader` 類用來讀取圖像。程式每次讀取圖像時，需要基於檔案資訊建立合適類型的 `ImageReader`。這個選擇邏輯可以包裝在一個簡單工廠中：

## 適用性

工廠方法，適用於面向介面編程（programming to interface）與實現依賴反轉原則。
下列情況可以考慮使用工廠方法模式：

* 建立物件需要大量重複的代碼。可以把這些代碼寫在工廠基礎類別中。
* 建立物件需要存取某些資訊，而這些資訊不應該包含在複合類中。
* 建立物件的生命周期必須集中管理，以保證在整個程式中具有一致的行為。 物件建立時會有很多參數來決定如何建立出這個物件。
* 建立物件可能是一個 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` 物件帶來的副作用。

## 局限性

使用工廠方法有三個局限，第一個與代碼重構有關，另外兩個與類的擴充有關。

* 第一個局限是，重構已經存在的類會破壞客戶端代碼。例如，`Complex` 類是一個標準的類，客戶端使用構造方法將其實例化。可能會有很多這樣的客戶端代碼：一旦 Complex 的編寫者意識到 `Complex` 的實例化應該使用工廠方法實現，他會將 `Complex` 的構造方法設為私有。而此時使用它的構造方法的客戶端代碼就都失效了。
* 第二個局限是，因為工廠方法所實例化的類具有私有的構造方法，所以這些類就不能擴充了。因為任何子類都必須呼叫父類別的構造方法，但父類別的私有構造方法是不能被子類呼叫的。
* 第三個局限是，如果確實擴充了工廠方法所實例化的類（例如將構造方法設為保護的，雖然有風險但也是可行的），子類必須具有所有工廠方法的一套實現。例如，在上述 `Complex` 的例子中，如果 `Complex` 有了一個子類 `StrangeComplex`，那麼 `StrangeComplex` 必須提供屬於它自己的所有工廠方法，否則將會返回一個 `Complex`（父類別）的實例，而不是所希望的子類別實例。但有些語言的反射特性可以避免這個問題。

通過修改底層程式語言，使工廠方法稱為第一類的類別成員，可以緩解這三個問題。

## 參考文獻

* Martin Fowler; Kent Beck; John Brant; William Opdyke; Don Roberts. Refactoring: Improving the Design of Existing Code. Addison-Wesley. 1999-06. ISBN 0-201-48567-2.
* Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. 1994. ISBN 0-201-63361-2.
* Cox, Brad J. Object-oriented programming: an evolutionary approach. Addison-Wesley. 1986. ISBN 978-0-201-10393-9.
* Cohen, Tal; Gil, Joseph. Better Construction with Factories (PDF). Journal of Object Technology (Bertrand Meyer). 2007, **6** (6): 103 [2007-03-12]. doi:10.5381/jot.2007.6.6.a3. （原始內容存檔 (PDF) 於 2021-03-09）.

## 參見

* 設計模式 (電腦)
* 抽象工廠
* 生成器模式
* 模板方法