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

时间:2010-05-24 12:10:46

标签: java .net design-patterns oop domain-driven-design

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

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

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

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

可以使用哪些其他策略来避免这些问题?

6 个答案:

答案 0 :(得分:19)

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

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

我建议的第二件事是阅读Single Responsibility Principle

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

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

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

从长远来看,你应该真正拿起Eric Evans的书Domain Driven Design。这是一个很好的阅读,但值得你花时间。我还建议您选择 Agile Software Development, Principles, Patterns, and PracticesAgile Principles, Patterns, and Practices in C#,具体取决于您的语言偏好。

答案 1 :(得分:10)

虽然真正的人类有很多责任,但你正朝着God object anti-pattern前进。

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

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

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

由于 便利性 ,您遇到了这个问题。即,将WebsiteUser视为聚合根并通过它访问所有内容很方便。

如果您更多地强调 清晰度 而不是 便利性 ,则应该有助于区分这些问题。不幸的是,这确实意味着团队成员现在必须了解新的服务。

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

希望有所帮助!

答案 2 :(得分:3)

一个非常简单的经验法则是“你班级中的大部分方法都必须使用你班级中的大多数实例变量” - 如果你遵循这个规则,那么这些类将自动大小合适。

答案 3 :(得分:2)

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

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

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

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

以这种方式思考。如果您有一个“Human”对象,则必须实现chew()方法。你会把它放在Human对象或Mouth子对象上。

答案 4 :(得分:2)

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

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();
    }
}

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

答案 5 :(得分:2)

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