2011-01-28 86 views
3

我從來沒有真正看過工廠模式,今天決定花費時間,並根據這篇文章(http://msdn.microsoft.com/en-us/library/ee817667.aspx)創建一個快速樣本,最終讓我的頭。工廠模式爲何如此工作?

源代碼完美地安排在三個單獨的組件中,整齊地命名爲Product,Factory和Client。

工廠模式的主要好處(據我所知)是從「客戶」類抽象「產品」類的實例化。因此,在所提供的示例中,產品實例化不會隨產品類的任何更改而發生變化,您仍然必須對客戶端類進行更改以傳遞創建更新產品所需的新值。這些數據畢竟必須來自某個地方?

我讀過的另一個例子說,一旦一個類被實現並且其他類的負載直接使用它,在這裏對「產品」類所做的更改將需要對該類的每個實例進行更改,比如說例如,如果在其構造函數中需要一個新變量。

從我所能理解的情況來看,Factory模式確實可以確保這個類的實例永遠不會改變,如果你想將一個新變量傳遞給產品構造函數,那麼你最終不得不將這些新變量傳遞給更新改爲工廠。

因此,這顯然不能解決問題,但僅僅移動它並且這樣做會增加額外的複雜性。

鑑於這是一種既定的模式,我顯然缺少一些東西。因此,這篇文章:請向我解釋我缺少的東西。

感謝

回答

8

工廠使用的時候可以有相同的接口的許多獨特的實現,它是決定只運行其中一個客戶的實際需要。但是,客戶端不需要知道它實際使用的是哪個實現。這是Factory進入的地方:它封裝了創建具體對象的細節,並將其作爲所需接口的通用實現返回。

實際上有兩種不同的模式與名稱Factory相關:Abstract FactoryFactory Method。後者用於創建單一產品的實例,而前者用於創建一整套相關產品。

抽象工廠的典型示例是在GUI框架中創建一系列小部件。框架的客戶端可能只需要知道他們正在處理一個窗口,一個狀態欄或一個按鈕;然而,它們不需要與實際小部件實際上是Windows還是MacOS小部件相關聯。這允許創建可以在這些平臺上運行的客戶端;在理論上,當框架被移植到新的平臺,比如說,Linux的,所有需要的是實現一個新的工廠產生的所有Linux特有的小部件,並通過配置插入。你瞧,客戶在Linux上運行沒有注意到任何區別,甚至可能無需重新編譯客戶端代碼(至少在理論上,而且在某些語言 - 我知道,對於多平臺的GUI現實是不同的,但是這僅僅是:-)

的例子比較這對試圖實現同樣的無工廠:在這裏你需要決定你需要建立一個特定於平臺的插件,你必須在客戶端代碼中的許多地方。並且,無論何時您想要引入新的小部件系列,您都需要修改代碼中的這些位置,以便爲許多相同的switchif/else塊添加新分支。此外,由於您會公開處理特定於平臺的窗口小部件對象,因此某些特定於平臺的窗口小部件的特性和實現細節可能泄漏到客戶端代碼中,因此更難以移植到其他平臺。

無論產品類別發生什麼變化,產品實例化都不會改變,您仍然需要對客戶端類別進行更改以傳遞zin創建更新產品所需的新值。這些數據畢竟必須來自某個地方?

確實。如果一般實例化過程改變,那麼Factory接口也可能需要相應地改變。這不是Factory的要點。儘管您可以在構建時將數據傳遞給工廠,但只要創建新產品,就可以在後臺使用這些數據。

+0

這也可能是很好的補充說,工廠模式通常處理相關類型的層次結構的創建,而不僅僅是一個。 – Grozz 2011-01-28 13:09:28

1

下面是在工廠模式維基的摘錄:

對象的創建往往需要複雜的工藝不適合於構成對象內包括。對象的創建可能會導致代碼的重複,可能需要組成對象無法訪問的信息,可能無法提供足夠的抽象級別,或者可能不會成爲組成對象關注的一部分。

所以有多層次的優勢,在具體情況下可能都存在,或者只是一些。

你是對的,一個工廠沒有解決必須傳遞所需構造函數參數的問題。然而,設想一個案例,其中這些參數需要複雜的計算來確定(您可能需要從數據庫中獲取值,或類似的東西)。因此,只需創建Product的實例,就需要在創建此類實例所需的每個位置都有相當一部分代碼。

對於那些複雜的對象實例,工廠模式發光,因爲剩餘的代碼與對象創建過程中涉及的複雜性無關。

下面是另一個粗略的想法來解釋它:對於一個簡單的對象,你只需使用new MyClass(some_arg)。如果實例化顯着更復雜,則需要多行代碼和可能的其他輔助方法。該工廠將其縮減回簡單的Factory.createMyClass(some_arg)

3

使用工廠是Dependency Inversion的一種形式,這是一種將客戶端與實現分離的方式。

例如,請考慮以下幾點:

class Client { 
    private DatabaseReader reader = new DatabaseReader(); 
    public void read() { 
     reader.read(); 
    } 
} 

凡DatabaseReader是一個具體的類。讓我們試着通過定義一個接口打破這種耦合:

class Client { 
    private Reader reader = new DatabaseReader(); 
    ... 
} 

幾乎有:

class Client { 
    private Reader reader = ReaderFactory.getInstance.getReader(); 
    ... 
} 

現在,客戶端不關心,如果它得到DatabaseReader,MemoryReader,等...它成爲ReaderFactory負責提供合適的Reader。

依賴注入需要更進一步,不再需要加載Factory類,而是在依賴注入容器中啓動代碼。

class Client { 
    @Inject 
    private Reader reader; 
    ... 
} 

在哪裏可以爲測試/運行環境聲明不同的接線。

1

在C++中,沒有虛擬構造函數。在Java這樣的語言中,這仍然不容易。這意味着人們必須始終確切地知道在創建對象時創建了哪種對象。這是插件的一個主要問題,因爲它阻止了將對象創建代碼抽象出來。

工廠模式解決了這個問題。一個只需要創建一次混凝土工廠。然後,人們可以通過對泛型代碼的抽象工廠(從中派生出具體工廠)的引用,並在需要創建具體對象時使用它,而無需確切知道它將創建的對象。

還有其他的好處。由於只有一個創建對象的位置,因此很容易存儲所有創建對象的列表。