如何避免使用域驱动设计的非常大的对象

我们正在关注领域驱动设计以实现大型网站。

但是,通过将行为放在域对象上,我们最终得到了一些非常大的类。

例如,在我们的WebsiteUser对象上,我们有许多方法 – 例如处理密码,订单历史记录,退款,客户细分。 所有这些方法都与用户直接相关。 其中许多方法内部委托给其他子对象但是
这仍然导致一些非常大的类。

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

还有什么其他策略可以避免这个问题?

您看到的问题不是由域驱动设计引起的,而是由于缺乏关注点分离。 领域驱动设计不仅仅是将数据和行为放在一起。

我建议的第一件事是花一天左右的时间阅读Domain Driven Design,可以从Info-Q免费下载。 这将概述不同类型的域对象:实体,值对象,服务,存储库和工厂。

我建议的第二件事是阅读单一责任原则 。

我建议的第三件事是你开始沉浸在测试驱动开发中 。 虽然通过首先编写测试来学习设计并不一定能让您的设计变得更好,但它们往往会引导您走向松散耦合的设计并更早地揭示设计问题。

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

由于缺乏业务环境,有点难以确切地说明你应如何处理你的设计,但我首先建议你做一些脑力激荡,创建一些代表你系统中每个主要名词的索引卡(例如客户,订单,收据,产品等)。 在顶部写下候选class级名称,你认为左边的class级所固有的责任,以及它将与右侧合作的class级。 如果某些行为感觉不属于任何对象,那么它可能是一个很好的服务候选者(即AuthenticationService)。 与你的大学一起在桌子上传播卡片并进行讨论。 不要过分夸大其实,因为这实际上只是作为头脑风暴的设计练习。 有时候这比使用白板要容易一些,因为你可以移动东西。

从长远来看,你应该选择Eric Evans的Domain Driven Design这本书。 这是一个很好的阅读,但值得你花时间。 我还建议您根据自己的语言偏好选择C#中的 敏捷软件开发,原则,模式和实践或敏捷原则,模式和实践 。

虽然真正的人类有很多责任,但你正朝向上帝对象的反模式 。

正如其他人所暗示的那样,您应该将这些职责提取到单独的存储库和/或域服务中 。 例如:

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

具体使用命名约定(即使用OrderRepositoryOrderService ,而不是OrderManager

你因为方便而遇到了这个问题。 WebsiteUser视为聚合根 ,并通过它访问所有内容很方便。

如果你更注重清晰度而不是方便 ,那么它应该有助于区分这些问题。 不幸的是,这确实意味着团队成员现在必须知道新的服务。

另一种思考方式:正如实体不应该执行自己的持久性 (这就是我们使用存储库的原因 ),您的WebsiteUser不应该处理退款/分段/等。

希望有所帮助!

我遇到了同样的问题,我发现在我们的案例中使用子“经理”对象是最好的解决方案。

例如,在您的情况下,您可能有:

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

然后,您可以将histMan用于任何您想要的任何内容。 显然你想到了这一点,但我不知道你为什么要避免它。 当你的对象看起来做得太多时,它会引起关注。

这样想吧。 如果你有一个“人”对象,你必须实现chew()方法。 你会把它放在Human对象或Mouth子对象上吗?

一个非常简单的经验法则是“你class级中的大部分方法都必须使用你class级中的大多数实例变量” – 如果遵循这个规则,这些类将自动具有正确的大小。

你可能想考虑反转一些事情。 例如,客户不需要拥有Order属性(或订单历史记录) – 您可以将这些属性保留在Customer类之外。 而不是

 public void doSomethingWithOrders(客户客户,日历,日历到){
     List = customer.getOrders(from,to);
     for(订单:订单){
         order.doSomething();
     }
 }

你可以做:

 public void doSomethingWithOrders(客户客户,日历,日历到){
     List = orderService.getOrders(customer,from,to);
     for(订单:订单){
         order.doSomething();
     }
 }

这是“松散”耦合,但您仍然可以获得属于客户的所有订单。 我相信有比我更聪明的人有正确的名字和链接参考上述内容。

我相信你的问题实际上与有界上下文有关。 对于我所看到的,“处理密码,订单历史,退款,客户细分”,这些中的每一个都可以是有限的上下文。 因此,您可以考虑将您的WebsiteUser拆分为多个实体,每个实体对应一个上下文。 可能会出现一些重复,但您可以专注于您的域并摆脱具有多重职责的非常大的类。