如何避免使用域驱动设计的非常大的对象
我们正在关注领域驱动设计以实现大型网站。
但是,通过将行为放在域对象上,我们最终得到了一些非常大的类。
例如,在我们的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)
具体使用命名约定(即使用OrderRepository或OrderService ,而不是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拆分为多个实体,每个实体对应一个上下文。 可能会出现一些重复,但您可以专注于您的域并摆脱具有多重职责的非常大的类。