2010-12-15 69 views
9

最近看過一些Greg Young視頻,我試圖理解爲什麼對域名對象上的Setters有負面態度。我認爲域對象應該是DDD邏輯的「重」。網上有不好的例子,然後他們的方式來糾正它嗎?任何例子或解釋都很好。這隻適用於以CQRS方式存儲的事件還是適用於所有的DDD?域名驅動設計,域名對象,對安裝者的態度

回答

11

我正在貢獻這個答案來補充Roger Alsing關於其他原因不變量的答案。

語義信息

羅傑解釋清楚,物業制定者不攜帶語義信息。允許像Post.PublishDate這樣的屬性的setter會添加混淆,因爲我們無法確定該帖子是否已發佈,或者只有在設置了發佈日期的情況下才能確定。我們無法確定這是發佈文章所需的全部內容。對象「界面」沒有清楚地顯示其「意圖」。

但我相信,像「啓用」這樣的屬性對於「獲取」和「設置」確實具有足夠的語義。這是應該立即生效的事情,並且由於缺少屬性設置器上的語義的原因,我看不到需要SetActive()或Activate()/ Deactivate()方法。

不變

羅格還談到通過屬性setter打破不變的可能性。這是絕對正確的,並且應該使得一起工作的屬性提供「組合不變值」(如使用Roger示例的三角形角度)作爲只讀屬性,並創建一個方法將它們全部設置在一起,這可以在一個步驟中驗證所有組合。

物業順序初始化/設置依賴

這類似於與不變的問題,因爲它會導致與需要驗證/更改一起性質的問題。想象下面的代碼:

public class Period 
    { 
     DateTime start; 
     public DateTime Start 
     { 
      get { return start; } 
      set 
      { 
       if (value > end && end != default(DateTime)) 
        throw new Exception("Start can't be later than end!"); 
       start = value; 
      } 
     } 

     DateTime end; 
     public DateTime End 
     { 
      get { return end; } 
      set 
      { 
       if (value < start && start != default(DateTime)) 
        throw new Exception("End can't be earlier than start!"); 
       end = value; 
      } 
     } 
    } 

這是導致訪問順序依賴性的「setter」驗證的一個天真的例子。下面的代碼說明了這個問題:

 public void CanChangeStartAndEndInAnyOrder() 
     { 
      Period period = new Period(DateTime.Now, DateTime.Now); 
      period.Start = DateTime.Now.AddDays(1); //--> will throw exception here 
      period.End = DateTime.Now.AddDays(2); 
      // the following may throw an exception depending on the order the C# compiler 
      // assigns the properties. 
      period = new Period() 
      { 
       Start = DateTime.Now.AddDays(1), 
       End = DateTime.Now.AddDays(2), 
      }; 
      // The order is not guaranteed by C#, so either way may throw an exception 
      period = new Period() 
      { 
       End = DateTime.Now.AddDays(2), 
       Start = DateTime.Now.AddDays(1), 
      }; 
     } 

既然我們不能改變的開始日期過去的結束日期起對象(除非它是一個「空」期間,隨着這兩個日期設置爲默認(DATETIME) - 是的,這不是一個很好的設計,但你明白我的意思......)首先嚐試設置開始日期會引發異常。

當我們使用對象初始值設定項時,它變得更加嚴重。由於C#不保證任何賦值順序,我們不能做出任何安全的假設,代碼可能會或可能不會拋出異常,具體取決於編譯器的選擇。壞!

這最終是類的設計問題。由於屬性無法「知道」您正在更新兩個值,因此無法「關閉」驗證,直到兩個值實際發生更改。您應該使這兩個屬性爲只讀並提供一種方法來同時設置這兩個屬性(丟失對象初始化程序的功能),或者從屬性中完全刪除驗證代碼(可能引入另一個只讀屬性,如IsValid或驗證它隨時需要)。

ORM 「水化」 *

水化,在一個簡單的觀點,意味着獲得持久化數據返回到對象。對我來說,這真的是在屬性設置器之後添加邏輯的最大問題。

許多/大多數ORM將持久值映射到屬性中。如果你有驗證邏輯或者邏輯改變了屬性設置器中的對象狀態(其他成員),你最終會試圖針對一個「不完整」的對象(一個仍然被加載)進行驗證。這與對象初始化問題非常相似,因爲您無法控制字段「水合」的順序。

大多數ORM允許您將持久性映射到私有字段而不是屬性,並且這將允許對象被水合,但是如果您的驗證邏輯主要位於屬性設置器內部,則可能必須在別處複製它以檢查加載的對象是否有效。

由於許多ORM工具通過使用映射到字段的虛擬屬性(或方法)支持延遲加載(ORM的基本方面),因此ORM不可能將延遲加載對象映射到字段。

結論

那麼,到底,以避免重複代碼,允許奧姆斯爲最好,因爲他們可以執行,防止視場的設置順序上令人驚訝的例外,這是明智的移動邏輯遠離產權人。

我還在搞清楚'驗證'邏輯應該在哪裏。我們在哪裏驗證對象的不變量方面?我們在哪裏進行更高級別的驗證?我們是否使用ORM上的鉤子來執行驗證(OnSave,OnDelete,...)?等等等但這不是這個答案的範圍。

+1

所以在你的例子中,我們應該使用'period.setDateBoundries(startDate,endDate)'這樣的單個函數來避免setter?一個具有商業含義的單一功能,而不是虛擬製定者? – Songo 2013-01-19 13:43:09

+1

@Songo那就是我會做的。但是可以抱怨必須通過兩個日期才能更改一個日期(想象一下,如果我們想延長一段時間,我們必須通過兩個日期!但是,嘿,爲什麼我們不創建一個Extend方法?)像這樣思考:無論何時你最終得到一個依賴於另一個的屬性,那麼引入方法來改變它們或者添加更多語義值的方法(比如Period.Extend ... )! – Loudenvier 2013-01-19 17:17:52

+0

我正在考慮傳遞數組。這對IDE提示並不好,但允許我以任何順序傳遞數據。我也可以根據情況添加或省略數據(即全日期跨度,或只是一個日期)。無論如何,驗證正在進行,所以看起來沒有太多的負擔來檢查是否給出或省略了參數。 – prograhammer 2015-08-03 15:51:17

1

一個setter只是設置一個值。它不應該是"heavy" with logic

你的物體上有很好的描述性名稱的方法應該是那些"heavy" with logic,並且在該域本身有一個類似物。

7

這背後的原因是實體本身應該負責改變其狀態。爲什麼你應該需要在實體本身之外的任何地方設置一個屬性,沒有任何理由。

一個簡單的例子就是一個有名字的實體。如果你有一個公共setter,你將能夠在你的應用程序的任何地方改變一個實體的名字。如果您改爲刪除該設置程序,並將ChangeName(string name)這樣的方法添加到您的實體中,這將是更改名稱的唯一方法。這樣,您可以添加任何種類的邏輯,當您更改名稱時,它們將始終運行,因爲只有一種方法可以更改它。這也是更清楚,然後只是設置名稱的東西。

基本上這意味着你公開你的實體的行爲,而你保持私人狀態。

+0

而且它將再次成爲一個設置者。但是在這種情況下,你有一個方法(或消息使用者)而不是屬性,它使你的域對象成爲活動的(而不僅僅是DTO)。以及爲什麼您需要(或不需要)在文章http://martinfowler.com/bliki/AnemicDomainModel.html – 2010-12-16 19:34:41

+0

@Vsevolod Parfenov,正是如此。現在這是一個非常簡單的例子。但有時您可能需要爲該方法添加某種邏輯。也許你還想更新一些其他領域。 – 2010-12-17 08:28:51

+0

屬性的SETTER只是一種方法的語法糖。如果一個屬性有邏輯,它也使得你的域對象成爲一個活動對象,而不僅僅是一個DTO,因爲實際上你剛剛添加了一個方法,儘管作爲屬性設置器是「隱藏」的。您仍然只有一個地方可以更改實體名稱:Name屬性的屬性設置器。所以這些不是對房產製定者持消極態度的原因。還有其他一些原因(我將在答案中詳細闡述):不變量,順序依賴性,以及ORM工具中保存實體時遇到的問題。 – Loudenvier 2012-06-13 14:35:21

2

原始問題標記爲「.net」,因此我將針對您希望將實體直接綁定到視圖的上下文提交務實的方法。我知道這是不好的做法,你應該有一個視圖模型(如在MVVM中)或類似的,但對於一些小應用程序,只是有意義的,不要過度模式化恕我直言。

使用屬性是開箱即用的.net數據綁定方式。也許上下文規定數據綁定應該兩種方式工作,因此實現INotifyPropertyChanged並將PropertyChanged作爲設置邏輯的一部分是合理的。

實體可以例如添加一個項目到一個破碎的規則集合或類似的東西(我知道CSLA有這樣的概念幾年前,也許還會這樣做),當客戶端設置一個無效的值,並且該集合可以顯示在用戶界面中。工作單位稍後會拒絕堅持無效對象,如果它應該到達那麼遠。

我試圖在很大程度上證明去耦,不變性等等。我只是說在某些情況下需要一個更簡單的架構。

+0

+1我爲你列出的確切原因而困惑於這個概念。熟悉Silverlight MVVM和一些CSLA的舊體驗。很想看到其他.NET開發人員的評論。 – BuddyJoe 2010-12-17 00:00:57

10

安裝程序不攜帶任何語義信息。

例如

blogpost.PublishDate = DateTime.Now; 

這是否意味着帖子已發佈? 或者只是發佈日期已設置?

考慮:

blogpost.Publish(); 

這清楚地表明瞭意圖博客帖子應該予以公佈。

此外,setter可能會破壞對象不變量。 例如,如果我們有一個「三角形」實體,不變量應該是所有角度的總和應該是180度。

Assert.AreEqual (t.A + t.B + t.C ,180); 

現在,如果我們有制定者,我們可以很容易地打破不變:

t.A = 0; 
t.B = 0; 
t.C = 0; 

因此,我們有一個三角形,所有角度的總和爲0 ... 那真的是一個三角形?我會說不。

與方法更換制定者可能會迫使我們保持不變:

t.SetAngles(0,0,0); //should fail 

這樣的調用應該拋出一個異常,告訴你,這會導致你的實體無效狀態。

所以你用方法而不是setters來獲得語義和不變量。

+0

所以只要我不打破使用setter的不變量是好的?我的意思是,如果用戶正在編輯客戶信息(名字,中間名,姓氏),唯一不變的是他們都不能爲空或NULL,那麼對每個字段使用setter是可以接受的? – Songo 2013-01-19 13:48:12

-1

我可能在這裏,但我相信setter應該用來設置,而不是setter方法。我有幾個原因。

a)它在.Net中是有意義的。每個開發人員都知道屬性。這就是你如何設置對象的方式。爲什麼偏離域對象?

b)安裝者可以有代碼。 3.5之前,我認爲,設置一個對象包括內部變量和屬性簽名

private object _someProperty = null; 
public object SomeProperty{ 
    get { return _someProperty; } 
    set { _someProperty = value; } 
} 

這是非常容易和國際海事組織優雅的把驗證的二傳手。在IL中,getter和setter被轉換成方法。爲什麼重複代碼?

在上面發佈的Publish()方法的例子中,我完全同意。有時我們不希望其他開發人員設置屬性。這應該通過一種方法來處理。然而,當.Net提供我們在屬性聲明中需要的所有功能時,爲每個屬性設置setter方法是否有意義?

如果您有Person對象,爲什麼在沒有原因的情況下爲其上的每個屬性創建方法?