2010-05-24 39 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

相关问题