工廠方法模式是一種實現了 工廠 概念的面向物件設計模式。就像其他建立型模式一樣，它也是處理在不指定物件具體類別的情況下建立物件的問題。工廠方法模式的實質是 定義一個建立物件的介面，但讓實現這個介面的類別來決定例項化哪個類別。工廠方法讓類別的例項化推遲到子類別中進行。 設計模式：可複用面向物件軟體的基礎 建立一個物件常常需要複雜的過程，所以不適合包含在一個複合物件中。建立物件可能會導致大量的重複程式碼，可能會需要複合物件訪問不到的資訊，也可能提供不了足夠級別的抽象，還可能並不是複合物件概念的一部分。工廠方法模式透過定義一個單獨的建立物件的方法來解決這些問題。由子類別實現這個方法來建立具體類別的物件。 物件建立中的有些過程包括決定建立哪個物件、管理物件的生命週期，以及管理特定物件的建立和銷燬的概念。 工廠 在面向物件程式設計中，工廠通常是一個用來建立其他物件的物件。工廠是構造方法的抽象，用來實現不同的分配方案。 工廠物件通常包含一個或多個方法，用來建立這個工廠所能建立的各種類別的物件。這些方法可能接收引數，用來指定物件建立的方式，最後返回建立的物件。 有時，特定類別物件的控制過程比簡單地建立一個物件更復雜。在這種情況下，工廠物件就派上用場了。工廠物件可能會動態地建立產品類別的物件，或者從物件池中返回一個物件，或者對所建立的物件進行復雜的配置，或者應用其他的操作。 這些類別的物件很有用。幾個不同的設計模式都應用了工廠的概念，並可以使用在很多語言中。例如，在《設計模式》一書中，像工廠方法模式、抽象工廠模式、生成器模式，甚至是單例模式都應用了工廠的概念。 程式碼舉例 例如，有一個 Button 類別表示按鈕，另有它的兩個子類別 WinButton 和 MacButton 分別代表 Windows 和 Mac 風格的按鈕，那麼這幾個類別和用於建立它們的工廠類別在 Java 中可以如下實現（在此省略所有類別和方法的可見性設定）： 幾個 Button 類 class Button class WinButton extends Button class MacButton extends Button 他們的工廠類別 interface ButtonFactory abstract Button createButton class WinButtonFactory implements ButtonFactory Button createButton return new WinButton class MacButtonFactory implements ButtonFactory Button createButton return new MacButton 其他舉例 在 ADONET 中，IDbCommandCreateParameter 使用工廠方法連線平行的類層次結構。 在 Qt，QMainWindowcreatePopupMenu 是在框架中定義的工廠方法，可以在應用程式碼中重寫。 在 Java 中，javaxxmlparsers 包使用了幾個工廠方法。例如 javaxxmlparsersDocumentBuilderFactory 和 javaxxmlparsersSAXParserFactory。 變種 雖然工廠方法模式的背後動機是允許子類別選擇建立物件的具體類別，但是使用工廠方法模式也有一些其他的好處，其中很多並不依賴於子類別。因此，有時候也會建立不使用多型性建立物件的工廠方法，以得到使用工廠方法的其他好處。 工廠「方法」而非工廠「類別」 如果拋開設計模式的範疇，工廠方法 這個詞也可以指作為 工廠 的方法，這個方法的主要目的就是建立物件，而這個方法不一定在單獨的工廠類別中。這些方法通常作為靜態方法，定義在方法所例項化的類別中。 每個工廠方法都有特定的名稱。在許多面向物件的程式語言中，構造方法必須和它所在的類別具有相同的名稱，這樣的話，如果有多種建立物件的方式（過載）就可能導致歧義。工廠方法沒有這種限制，所以可以具有描述性的名稱。舉例來說，根據兩個實數建立一個複數，而這兩個實數表示直角座標或極座標，如果使用工廠方法，方法的含義就非常清晰了。當工廠方法起到這種消除歧義的作用時，構造方法常常被設定為私有方法，從而強制客戶端程式碼使用工廠方法建立物件。 下面的例子展示了在不同的程式語言中實現複數建立的程式碼： Java class Complex public static Complex fromCartesianFactory double real double imaginary return new Complex real imaginary public static Complex fromPolarFactory double modulus double angle return new Complex modulus cos angle modulus sin angle private Complex double a double b Complex product ComplexfromPolarFactory 1 pi VBNET Public Class Complex Public Shared Function fromCartesianFactory real As Double imaginary As Double As Complex Return New Complex real imaginary End Function Public Shared Function fromPolarFactory modulus As Double angle As Double As Complex Return New Complex modulus MathCos angle modulus MathSin angle End Function Private Sub New a As Double b As Double End Sub End Class Complex product ComplexfromPolarFactory 1 pi C public class Complex public double Real public double Imaginary public static Complex FromCartesianFactory double real double imaginary return new Complex real imaginary public static Complex FromPolarFactory double modulus double angle return new Complex modulus MathCos angle modulus MathSin angle private Complex double real double imaginary Real real Imaginary imaginary var product ComplexFromPolarFactory 1 pi 簡單工廠 普通的工廠方法模式通常伴隨著物件的具體類別與工廠具體類別的一一對應，客戶端程式碼根據需要選擇合適的具體類別工廠使用。然而，這種選擇可能包含複雜的邏輯。這時，可以建立一個單一的工廠類別，用以包含這種選擇邏輯，根據引數的不同選擇實現不同的具體物件。這個工廠類別不需要由每個具體產品實現一個自己的具體的工廠類別，所以可以將工廠方法設定為靜態方法。 而且，工廠方法封裝了物件的建立過程。如果建立過程非常複雜（比如依賴於配置檔案或使用者輸入），工廠方法就非常有用了。 比如，一個程式要讀取影像檔案。程式支援多種影像格式，每種格式都有一個對應的 ImageReader 類別用來讀取影像。程式每次讀取影像時，需要基於檔案資訊建立合適類別的 ImageReader。這個選擇邏輯可以包裝在一個簡單工廠中： public class ImageReaderFactory public static ImageReader imageReaderFactoryMethod InputStream is ImageReader product null int imageType determineImageType is switch imageType case ImageReaderFactoryGIF product new GifReader is case ImageReaderFactoryJPEG product new JpegReader is return product 適用性 工廠方法，適用於面向介面程式設計（programming to interface）與實現依賴反轉原則。 下列情況可以考慮使用工廠方法模式： 建立物件需要大量重複的程式碼。可以把這些程式碼寫在工廠基類別中。 建立物件需要訪問某些資訊，而這些資訊不應該包含在複合類別中。 建立物件的生命週期必須集中管理，以保證在整個程式中具有一致的行為。 物件建立時會有很多引數來決定如何創建出這個物件。 建立物件可能是一個 pool 裡的，不是每次都憑空建立一個新的。而 pool 的大小等引數可以用另外的邏輯去控制。比如連線池物件，執行緒池物件 業務物件的程式碼作者希望隱藏物件的真實類別，而建構函式一定要真實的類別名才能用 簡化一些常規的建立過程。根據配置去建立一個物件也很複雜；但可能 95 的情況只建立某個特定類別的物件。這時可以弄個函式直接省略那些配置過程。如 Java 的執行緒池的相關建立 api（如 ExecutorsnewFixedThreadPool 等） 建立一個物件有複雜的依賴關係，比如 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 c new Complex 1 0 一旦 Complex 的編寫者意識到 Complex 的例項化應該使用工廠方法實現，他會將 Complex 的構造方法設為私有。而此時使用它的構造方法的客戶端程式碼就都失效了。 第二個侷限是，因為工廠方法所例項化的類別具有私有的構造方法，所以這些類別就不能擴充套件了。因為任何子類別都必須呼叫父類別的構造方法，但父類別的私有構造方法是不能被子類別呼叫的。 第三個侷限是，如果確實擴充套件了工廠方法所例項化的類別（例如將構造方法設為保護的，雖然有風險但也是可行的），子類別必須具有所有工廠方法的一套實現。例如，在上述 Complex 的例子中，如果 Complex 有了一個子類別 StrangeComplex，那麼 StrangeComplex 必須提供屬於它自己的所有工廠方法，否則 StrangeComplexfromPolar 1 pi 將會返回一個 Complex（父類別）的例項，而不是所希望的子類別例項。但有些語言的反射特性可以避免這個問題。 透過修改底層程式語言，使工廠方法稱為第一類別的類別成員，可以緩解這三個問題。 參考文獻 參見 設計模式 計算機 抽象工廠 生成器模式 模板方法