2010-08-24 55 views
6

當我創建類時,簡單的構造函數往往是常態。在我目前的一個項目中,有一個電影庫,我有一個Movie域對象。它有許多屬性,產生如下構造函數:構建一個(有點)複雜的對象

public Movie(string title, int year, Genre genre, int length, IEnumerable<string> actors) 
{ 
    _title = title; 
    _year = year; 
    _genre = genre; 
    _length = length; 
    _actors = new List<string>(actors); 
} 

這並不可怕,但它也不簡單。使用工廠方法(static Movie CreateMovie(...))還是一個對象構建器會值得嗎?有實例化域類的典型模式嗎?

UPDATE:感謝您的回覆。儘管我已經學到了一些在更復雜的情況下將會有用的東西,但我最初可能會反思這個問題。我現在的解決方案是將標題作爲唯一必需的參數,其餘爲命名/可選參數。這似乎是構建這個域對象的全面理想方式。

+0

您的問題的解決方案不應作爲您的問題的更新發布。它應該是一個答案,或者在這種情況下,可能是一個評論。 – 2013-01-15 20:12:48

回答

4

如果您使用的是.NET 4.0,,則可以使用optional/named parameters來簡化接受多個參數的對象的創建,其中一些參數是可選參數。當你想避免許多不同的重載提供有關對象的必要信息時,這很有用。

如果你不在.NET 4,你可能想使用Object Builder模式來組裝你的類型。對象構建器需要一點努力才能實現,並與您保持同步類型 - 所以這樣做是否有足夠的價值取決於您的情況。

我發現生成器模式在彙編層次結構時最有效,而不是具有一堆屬性的類型。在後一種情況下,我通常是重載或可選/命名參數。

+0

我目前使用3.5,但4.0是一個選項。我也嘗試過建設者的想法,這是一個工作,並且在這種情況下似乎是矯枉過正。雖然我可以看到它在您提到的更復雜的設置中很有用。 – 2010-08-24 17:38:43

1

您對自己的問題給出了很好的答案,這是工廠模式。使用工廠模式,您不需要巨大的構造函數進行封裝,您可以在工廠函數中設置對象的成員並返回該對象。

1

這是完全可以接受的,恕我直言。我知道靜態方法有時會被忽略,但我通常會將該代碼放入一個返回該類實例的靜態方法中。我通常只對那些允許有空值的對象做這件事。

如果對象的值不能爲null,則將它們作爲參數添加到構造函數中,這樣就不會有任何浮動的無效對象。

3

這取決於。

如果這是該類的唯一構造函數,則意味着所有屬性都是實例化該對象所必需的。如果這符合您的業務規則,那就太棒了。否則,可能會有點麻煩。例如,如果你想用電影播種你的系統,但並不總是有演員,你可能會發現自己陷入了泡菜。

您提到的CreateMovie()方法是另一種選擇,以防您需要將內部構造函數與創建Movie實例的操作分開。

您有許多選項可供您安排構造函數。使用那些允許你設計沒有氣味和多種原理的系統(DRY,YAGNI,SRP)。

+0

「用電影種子你的系統,但並不總是有演員」 - 優點。我曾考慮過忽略這個參數並使用'AddActor(string name)'方法。 – 2010-08-24 17:34:40

+0

我開始考慮把標題作爲唯一的ctor。參數,剩下的可設置的屬性,所以我可以輕鬆地添加電影到系統並在稍後填寫細節。 – 2010-08-24 17:36:32

+0

我通常不喜歡將屬性放在任何構造函數中,因此我的選項保持打開狀態。使用新的C#對象初始化語法處理屬性賦值非常容易:[new object(){fooProp =「1」,barProp =「Hello」}] – 2010-08-24 17:44:18

4

是的,使用工廠方法是一種典型的模式,但問題是:爲什麼你需要它?這是Wikipedia says關於工廠方法:

像其他創建型模式,它沒有指定確切類對象將創建創建對象(產品)的問題涉及。工廠方法設計模式通過定義用於創建對象的單獨方法來處理此問題,然後可以重寫該子類以指定將創建的產品的派生類型。

所以,如果你想返回子類Movie工廠方法模式纔有意義。如果這不是(也不是)要求,用工廠方法替換公共構造函數並不真正起到任何作用。

對於您的問題中陳述的要求,您的解決方案對我來說看起來非常好:所有必填字段都作爲參數傳遞給構造函數。如果您的字段都不是必填字段,則可能需要添加默認初始值設定項並使用C# object initializer syntax

+0

工廠方法的優點。這是我所知道的一種創造性技術,所以我想我會把它扔進戰場。鑑於引用,這裏似乎沒有必要。 – 2010-08-24 17:50:37

1

我沒有看到你的構造函數的接口有什麼問題,也沒有看到什麼是靜態方法。我將擁有完全相同的參數,對嗎?

參數似乎不是可選的,所以沒有辦法使用可選參數來減少過載或使用可選參數。

從點,查看來電者的,它看起來是這樣的:

Movie m = new Movie("Inception", 2010, Genre.Drama, 150, actors); 

工廠的目的是爲您提供一個界面的定製具體的實例,而不是隻是調用構造函數爲你。這個想法是,確切的類沒有硬編碼的建設點。這真的好嗎?

Movie m = Movie.Create("Inception", 2010, Genre.Drama, 150, actors); 

這對我來說看起來差不多。唯一更好的是如果Create()返回其他具體的類比Movie

有一點需要考慮的是如何改善這一點,以便調用代碼很容易理解。對我來說最明顯的問題是,沒有看到代碼爲Movie的代碼並不明顯。有幾種方法來改善,如果你想:

  1. 使用類型電影的長度和構建型直列新MovieLength(150)
  2. 使用named parameters if you are using .NET 4.0
  3. (見@ Heinzi的答案)使用對象初始化
  4. 使用fluent interface

有了一個流暢的界面,您的通話將類似於

Movie m = new Movie("Inception"). 
    MadeIn(2010). 
    InGenre(Genre.Drama). 
    WithRuntimeLength(150). 
    WithActors(actors); 

坦率地說,所有這些對你的情況來說似乎有些過火。如果您使用的是.NET 4.0,命名參數是合理的,因爲它們不是那麼多的代碼,並且會改善調用者的代碼。

1

我沒有看到任何錯誤的方式離開公共構造函數。在決定是否採用工廠方法時,我傾向遵循一些規則。

  • 當初始化需要複雜算法時,請使用工廠方法。
  • 當初始化需要IO綁定操作時,請使用工廠方法。
  • 當初始化可能會拋出一個在開發時無法防範的異常時,請使用工廠方法。
  • 如果需要額外的Verbage以增強可讀性,請使用工廠方法。

所以根據我自己的個人規則,我會讓構造函數保持原樣。

0

至於我 - 全部取決於您的域模型。如果你的域模型允許你創建簡單的對象 - 你應該這樣做。

但是我們經常會有很多複合對象,每個對象的創建過於複雜。這就是爲什麼我們正在尋找封裝組合對象創建邏輯的最佳方式。實際上,我們只有兩種選擇 - 「工廠方法」和「對象構建器」。通過靜態方法創建對象看起來有點奇怪,因爲我們將對象創建邏輯放入對象中。對象構建器反過來看起來很複雜。

我認爲答案在於單元測試。當TDD非常有用時,情況正是如此 - 我們逐步建立我們的領域模型並瞭解領域模型複雜性的需求。

1

如果您可以從配置參數中區分核心數據成員,則創建一個構造函數,以獲取所有核心數據成員,而不是其他任何地方(甚至不包含默認值—爲便於閱讀而配置的配置參數)。初始化配置參數以保持缺省值(在方法體內)並提供setter。在那個時候,如果你想要的對象有共同的配置,工廠方法可以爲你購買一些東西。

更好的是,如果您發現有一個對象需要大量參數,則該對象可能太胖。你已經聞到了你的代碼可能需要重構的事實。考慮分解你的對象。關於OO的良好文獻強烈地爭論小物體(例如Martin Fowler,重構; Bob Martin,Clean Code)。福勒解釋如何分解大對象。例如,配置參數(如果有的話)可能表明需要更多的多態性,特別是如果它們是布爾值或枚舉(重構「轉換條件到多態性」)。

在給出更具體的建議之前,我需要查看對象的使用方式。福勒說,一起使用的變量應該成爲他們自己的對象。所以,爲了說明起見,如果您根據流派,年份和長度計算某些事物,但不計算其他屬性,那麼可能需要將它們一起分解到它們自己的對象中。減少必須參數的數量被傳遞給你的構造函數。