2010-05-24 103 views
26

我們正在關注域驅動設計以實現大型網站。如何避免使用域驅動設計創建超大型對象

但是通過將行爲放在域對象上,我們最終得到了一些非常大的類。

例如在我們的WebsiteUser對象上,我們有很多很多的方法 - 例如,處理密碼,訂單歷史記錄,退款,客戶細分。所有這些方法都與用戶直接相關。許多這些方法在內部委託給其他子對象,但
這仍然導致一些非常大的類。

我很想避免暴露很多子對象 例如user.getOrderHistory()。getLatestOrder()。

還有哪些策略可以用來避免這個問題?

回答

2

我遇到了同樣的問題,我發現在我們的例子中,使用子「管理」對象是最好的解決方案。

例如,在你的情況,你可能有:

User u = ...; 
OrderHistoryManager histMan = user.getOrderHistoryManager(); 

然後你就可以使用histMan爲你想要的任何東西。顯然你想到了這一點,但我不知道你爲什麼要避免它。當你有似乎做得太多的物體時,它會分擔關注。

想想這樣。如果您有一個「人」對象,並且您必須實施chew()方法。你會把它放在Human對象還是Mouth子對象上。

19

您看到的問題不是由域驅動設計引起的,而是由於缺乏關注點分離引起的。領域驅動設計不僅僅是將數據和行爲放在一起。

我推薦的第一件事就是花一天左右的時間閱讀Domain Driven Design Quickly,並從Info-Q免費下載。這將提供不同類型域對象的概述:實體,值對象,服務,存儲庫和工廠。

我推薦的第二件事是去讀Single Responsibility Principle

我建議的第三件事是你開始沉浸在Test Driven Development。儘管先通過編寫測試來學習設計並不一定會讓你的設計變得更好,但他們傾向於引導你走向鬆散耦合的設計並在早期揭示設計問題。

在您提供的示例中,WebsiteUser肯定有太多的責任。事實上,您可能根本不需要WebsiteUser,因爲用戶通常由ISecurityPrincipal代表。

由於缺乏商業背景,我們很難確切地建議您如何處理您的設計,但我會首先建議您通過創建一些索引卡片來代表您的系統中每個主要名詞的索引卡片(如客戶,訂單,收據,產品等)。在頂部寫下候選人的名字,你感覺到的是左邊班級固有的責任,以及與右邊合作的班級。如果某些行爲不覺得它屬於任何對象,那麼它可能是一個很好的服務候選者(即AuthenticationService)。與學院一起將卡片放在桌子上討論。不要太多這樣做,因爲這實際上只是一個頭腦風暴的設計練習。與使用白板相比,這樣做有時會更容易,因爲您可以四處移動。

長期來看,你真的應該拿起埃裏克埃文斯的書Domain Driven Design。這是一個很大的閱讀,但非常值得你的時間。我還建議您根據您的語言偏好選擇 Agile Software Development, Principles, Patterns, and PracticesAgile Principles, Patterns, and Practices in C#

+0

感謝您的評論。我讀過埃文斯先生的書。我猜問題是一個實體有很多合作者。例如網站用戶(或校長)具有最新的訂單,第一訂單,購物車,密碼等。所有這些都與網站用戶直接相關。 – Pablojim 2010-05-24 14:11:19

+1

身份驗證,掛單狀態(即購物車)和訂單歷史確實是獨立的關注點。考慮將身份驗證拉出到IAuthenticationService中,並創建一個IOrderHistoryRepository(或者IOrderHistoryService)來封裝給定用戶的檢索訂單歷史記錄的行爲。 – 2010-07-14 15:48:28

2

要遵循的一個非常簡單的經驗法則是「大部分類中的方法都必須使用類中的大部分實例變量」 - 如果遵循此規則,類將自動具有合適的大小。

+0

這是一個有趣的觀察。你能提供任何鏈接嗎? – ya23 2010-05-25 15:44:58

+0

它基本上是一種更簡單的方法來記住SRP - 我在其中一本書中讀到它,但發現它非常有趣,它卡在我的腦海裏。 – OpenSource 2010-05-25 19:55:59

+0

這也被稱爲高凝聚力 – beluchin 2013-01-19 21:10:47

2

你可能想考慮顛倒一些東西。例如,客戶不需要擁有訂單屬性(或訂單歷史記錄) - 您可以將其留在客戶類別之外。因此,而不是

 
public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) { 
    List = customer.getOrders(from, to); 
    for (Order order : orders) { 
     order.doSomething(); 
    } 
} 

可以轉而做:

 
public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) { 
    List = orderService.getOrders(customer, from, to); 
    for (Order order : orders) { 
     order.doSomething(); 
    } 
} 

這是「寬鬆」耦合,但你仍然可以得到所有屬於客戶的訂單。我確信有比我更聰明的人擁有正確的名字和鏈接,指的是上述內容。

9

雖然真人有很多責任,但您正朝着God object anti-pattern前進。

正如其他人暗示,你應該提取這些責任到單獨和/或域服務。例如: -

SecurityService.Authenticate(credentials, customer) 
OrderRepository.GetOrderHistoryFor(Customer) 
RefundsService.StartRefundProcess(order) 

就必須明確命名約定(即使用OrderRepositoryOrderService,而不是的OrderManager

你碰到的這個問題,因爲方便。即將WebsiteUser視爲聚合根並且通過它訪問所有內容是方便的。

如果你更重視清晰的代替方便,它應該幫助分開這些問題。不幸的是,這意味着團隊成員現在必須瞭解新的服務。

另一種方式把它:就像實體不應執行自己持久(這就是爲什麼我們使用),您WebsiteUser不應辦理退款/分割/等。

希望有幫助!

1

我相信你的問題實際上與有界上下文有關。對於我所看到的「處理密碼,訂單歷史,退款,客戶細分」,其中每一個都可能是一個有界的上下文。因此,您可以考慮將您的WebsiteUser分成多個實體,每個實體對應一個上下文。可能會出現一些重複,但您將重點放在了您的域名上,並擺脫了承擔多重責任的大型課程。

+0

+1這非常接近DCI與DCI的樣子。將系統與系統的功能分開。 – Gordon 2013-04-22 11:00:53

相關問題