贫困领域模型:优点/缺点

时间:2008-11-03 12:49:34

标签: language-agnostic design-patterns

我想知道使用贫血领域模型的优缺点(见下面的链接)。

Fowler Article

17 个答案:

答案 0 :(得分:128)

“贫血领域模型”是反模式,为什么有这么多系统实现这个?

我认为有几个原因

<强> 1。系统的复杂性

如果我想实现一个简单的系统(几乎所有的例子和你在互联网上找到的示例代码):

将产品添加到订单

我将此功能放在订单

public void Order.AddOrderLine(Product product)
{
    OrderLines.Add(new OrderLine(product));
}

很好,超级面向对象。

现在让我们说我需要确保我需要验证产品是否存在于库存中,如果不存在则抛出异常。

我不能再把它放在订单上,因为我不希望我的订单依赖于库存,所以现在它需要继续服务

public void OrderService.AddOrderLine(Order order, Product product)
{
    if (!InventoryService.Has(product)
       throw new AddProductException

    order.AddOrderLine(product);
}

我也可以将IInventoryService传递给Order.AddOrderLine,这是另一种选择,但仍然使Order依赖于InventoryService。

Order.AddOrderLine中仍然存在一些功能,但通常仅限于订单范围,而根据我的经验,还有更多业务逻辑无序订单范围。

当系统不仅仅是基本的CRUD时,您将在OrderService中获得大部分逻辑,而在Order中则很少。

<强> 2。开发人员对OOP的看法

互联网上有很多关于哪些逻辑应该在实体上进行讨论的热烈讨论。

这样的东西

Order.Save

订单是否应该知道如何自救?假设我们有这个存储库。

现在可以订购添加订单行吗?如果我试着用简单的英语来理解它,它也没有意义。用户将产品添加到订单中,那么我们应该使用User.AddOrderLineToOrder()吗?这似乎有点矫枉过正。

OrderService.AddOrderLine()怎么样?现在它有点意义了!

我对OOP的理解是,对于封装,你将函数放在函数需要访问类的内部状态的类上。如果我需要访问Order.OrderLines集合,我将Order.AddOrderLine()放在Order上。这样,类的内部状态不会暴露。

第3。 IoC容器

使用IoC容器的系统通常都是充满贫血的。

这是因为您可以测试具有接口的服务/存储库,但不能(轻松地)测试域对象,除非您将接口放在所有接口上。

由于“IoC”目前被认为是解决所有编程问题的解决方案,因此很多人盲目地遵循它,这样最终会出现Anemic Domain Models。

<强> 4。 OOP很难,程序很容易

我对此有一点“Curse of Knowledge”,但我发现了这一点 对于拥有DTO和服务的新开发人员来说,比Rich Domain容易得多。

可能是因为使用Rich Domain,更难以知道哪些类放置逻辑。何时创建新类?使用哪种模式?等。

使用无状态服务,您只需在具有最接近名称的服务中使用它。

答案 1 :(得分:36)

专业人士:

  • 您可以声称它是域模型 并吹嘘你的开发者朋友 把它放在你的简历上。
  • 自动生成很容易 来自数据库表。
  • 它映射到数据传输对象 非常好。

缺点:

  • 您的域逻辑存在于某处 否则,可能在一个完整的课堂上 class(静态)方法。或者您的GUI 码。或者在多个地方,都存在冲突的逻辑。
  • 这是反模式,所以其他 开发人员会询问你 理解对象的概念 导向设计。

答案 2 :(得分:20)

在此之后,我脑子里想了很长时间。我相信“OOP”这个词的含义并不是真正意义上的。正如我们所熟知的那样,anagram意味着“面向对象的编程”。当然,重点是“定向”一词。它不是“OMP”,意思是“对象强制编程”。 ADM和RDM都是OOP的例子。它们使用对象,属性,方法接口等。但是,ADM和RDM在我们选择如何封装事物方面存在差异。他们是两个不同的东西。要说ADM是坏的OOP并不是一个准确的说法。也许我们需要不同的术语来代替各种级别的封装。另外,我从不喜欢反模式这个词。它通常由对立组的成员分配给某事物。 ADM和RDM都是有效的模式,它们简单地考虑了不同的目标,旨在解决不同的业务需求。我们这些练习DDD的人至少应该理解这一点,而不是通过抨击那些选择实施ADM的人来达到其他人的水平。只是我的想法。

答案 3 :(得分:15)

“这是一种反模式,所以其他开发人员会问你是否理解面向对象设计的概念。”

“贫血领域模式是一种反模式。反模式没有优势。”

贫血领域模型是否是反模式是一个意见问题。 Martin Fowler表示,许多开发人员都知道内部OO说它不是。 将意见陈述为事实很少有用。

即使它被普遍认为是一种反模式,它仍有可能仍有一些(虽然相对较小)的上升空间。

答案 4 :(得分:13)

在我看来,福勒的主要反对意见是ADM在以下意义上不是OO。如果设计一个系统“从头开始”围绕被其他代码操纵的被动数据结构,那么这肯定会比程序设计更像是面向对象的设计。

我认为至少有两种力量可以产生这种设计:

  1. 设计师/程序员仍然认为在程序上需要在面向对象的环境中工作(或假设他们可以......)来生成 new 系统,并且

  2. 开发人员致力于将服务式“面子”放在以非OO方式设计的传统系统上(无论语言如何)。

  3. 例如,如果一个人正在构建一组服务来公开现有COBOL大型机应用程序的功能,那么可以根据概念模型定义服务和接口,该模型可以镜像内部COBOL数据结构。但是,如果服务将新模型映射到遗留数据以使用现有但隐藏的实现,那么在Fowler的文章中,新模型很可能是“贫血” - 例如一组TransferObject风格的定义和关系,没有真正的行为。

    这种妥协很可能是理想主义纯粹的OO系统必须与现有的非OO环境相互作用的边界。

答案 5 :(得分:7)

如果您的团队无法或不愿意构建富域模型(RDM)并随着时间的推移维护它,那么贫血域模型(ADM)可能是一个不错的选择。使用RDM获胜需要仔细关注系统中使用的主要抽象。图中,在任何开发组中,不超过一半,也许只有十分之一的成员能够胜任抽象。除非这个干部(也许只有一个开发者)能够保持对整个集团活动的影响,否则RDM将屈服于熵。

熵RDM特别受到伤害。它的开发人员将学习苛刻的课程。起初,他们将能够满足利益相关者的期望,因为他们没有历史可以实现。但随着他们的系统越来越complicated (not complex),它将变得脆弱;开发人员将尝试重用代码,但往往会在开发中引发新的错误或回溯(从而超出他们的估计)。

相比之下,ADM开发人员会对自己设定较低的期望,因为他们不会期望重用新功能的代码。随着时间的推移,他们将拥有一个存在许多不一致的系统,但它可能不会无法突破。他们的上市时间比成功的RDM更长,但他们的利益相关者不太可能认识到这种可能性。

答案 6 :(得分:5)

“开发人员致力于在非OO方式设计的遗留系统(不论语言)上设置类似服务的”面孔“。

如果您考虑许多LOB应用程序,这些遗留系统通常不会使用与您相同的域模型。 Anemic Domain Model通过在服务类中使用业务逻辑来解决这个问题。您可以将所有这些接口代码放入模型中(在传统的OO意义上) - 但您通常最终会失去模块化。

答案 7 :(得分:4)

当我第一次看到“贫血领域模型”文章时,我认为“圣洁的***,这就是我的所作所为。恐怖!”我坚持并遵循Eric Evan的书,这是一个很好的例子,并下载了源代码。事实证明,“不使用贫血领域模型”并不意味着“不使用服务类,不使用调解器,不使用策略”,甚至“将逻辑放在被操纵的类上”。

DDD示例包含服务类,XyzUpdaters,单例和IoC。

我仍然对一个贫血领域模型究竟是什么感到困惑。我希望“当我看到它时,我会知道它”。现在我满足于良好设计的积极榜样。

答案 8 :(得分:3)

它与大多数反模式一样专业:它可以让你让很多人忙碌很长时间。由于管理者在管理更多人时往往会获得更多报酬,因此有强烈的动机不会改善。

答案 9 :(得分:1)

根据Eric P的回答以及上面其他人写的内容,似乎ADM的主要缺点是OOD的丢失,特别是将域概念的逻辑和数据保持在一起,以便实现细节在API可以丰富时隐藏。

Eric继续指出,域类之外的信息通常是对该类操作的逻辑所必需的,例如在向订单添加项目之前检查库存。不过,我怀疑答案是否是一个包含这个总体逻辑的服务层,或者它是否更好地作为对象设计的一部分来处理。 有人必须知道Inventory对象,Product对象和Order对象。也许它只是一个OrderSystem对象,它有一个Inventory成员,一个Orders列表等等。这与服务看起来并没有什么不同,但我认为它在概念上更加连贯。

或者以这种方式看待它:你可以让一个具有内部信用余额的用户,并且每次调用User.addItemToOrder(item)时,它都会获得项目的价格并在添加之前检查信用,等等。合理的OO设计。我不确定用Service.addItemToUserOrder(用户,项目)替换它会失去什么,但我不确定是什么获得的。我想损失将是额外的代码层,加上笨拙的写作风格和对底层域模型的强制无知。

答案 10 :(得分:1)

当我第一次阅读Eric Evans的关于域驱动设计的书时,我并没有真正理解,这不仅仅是用于设计好的域模型类的一堆战术模式。

在学习了有关该主题的更多信息并使用了战略模式之后,我终于开始了解起初这是要对您要解决的业务问题有深入的了解。 / p>

只有在此之后,您才能决定系统的哪些部分适合于应用战术模式,例如集合,实体,存储库等,以及所谓的< em> 丰富 域模型(与贫血症相对)。但是,要从这些模式中受益,必须为系统的那部分提供足够的与业务逻辑有关的复杂度

因此,在实施眼前问题的解决方案时,应首先确定是否可以通过使用基于 CRUD 的方法或投资于 <

如果CRUD更有意义,例如如果没有复杂的业务逻辑,并且大多数逻辑都与实现域模型的转换,传输和持久化数据有关,那将是不必要的过大杀伤力。这并不意味着不会有很多工作要做,而仅仅是产生最大工作量的不是业务规则。 但是在这种情况下,根本就没有贫血领域模型 ,仅仅是因为 根本没有领域模型 > 。您宁愿看到的是诸如 DTO (数据传输对象)或 DAO (数据访问对象)以及将对数据进行操作的服务类之类的东西。而且,相应的操作在很大程度上与将数据从一种表示形式转换为另一种表示形式以及在几乎没有或几乎没有业务逻辑的情况下移动数据有关。

如果您确定存在许多复杂的业务逻辑,这些逻辑也会随着时间的推移而变化,而不是投资领域模型-根据我的经验,这是个好主意。原因是更容易通过代码表示业务透视图,并且更容易理解反映业务域及其规则的相应操作。这并不意味着每个用例都必须有域模型类。例如,如果不存在任何要被突变和持久化的状态,那么也只能将包含域逻辑的域服务实现得更像纯函数。

但是,如果在业务领域中也存在具有目的和意义的,要变异和持久的状态,则应该对该状态和更改该状态的行为进行封装。这样一来,没有人能绕过容易的业务规则,从而导致无效状态以及严重的故障。 所谓的 micic 域模型通常是此类问题的来源。如果您看到代码,其中不同的组件在相同的“贫乏”域模型类上运行,则通常会遇到这种情况,即检查其状态的某些部分并更改其状态的某些部分,而无需关心或了解该业务实体的整体不变性。不必将其称为反模式,但重要的是要了解,在基于DDD的方法中,您会失去 rich 域模型的许多好处问题。当使用将行为及其数据放在同一个类中的域模型时,也可以有很多不同的“客户端”调用该类的操作,但是他们不必关心遵守业务实体的业务不变量,因为域模型类将始终处理该问题,并且还可以通知“客户端”无效操作,甚至将异常作为安全网抛出。

因此,底线,我认为不要将诸如类(如DTO或DAO)之类的数据结构与贫血的领域模型类混为一谈很重要。在精心选择的基于CRUD的方法中,尝试使用域模型没有任何优势,因为业务逻辑太简单了。

通过贫血域模型我将参考代码,从中我可以看到有很多复杂的业务逻辑和业务规则分布在各个组件之间,而这些组件应该更接近数据这些逻辑正在改变。

在此过程中,我还吸取了另一课:如果您尝试在代码中使用相同的业务语言(也称为 Ubiquituous Language ),如果牛排馆员在日常工作中使用,您已经在理解业务领域和提高代码的可读性方面赢得了许多优势,无论您使用基于CRUD还是基于域模型的方法,都将为您带来很大帮助。

答案 11 :(得分:1)

与一个成熟的&#39;有ADM的系统,我觉得我可以提供一些,至少是对这个问题的轶事反馈。

1)缺乏封装

在带有ADM的实时系统中,有可能写入例如&#39; obj.x = 100; obj.save&#39;,即使这违反了业务逻辑。如果在对象上建模不变量,这会导致许多错误。我认为这些错误的严重性和普遍性是对ADM最严重的负面影响。

我觉得重要的是在这里指出,这是一个功能性解决方案,ADM的程序解决方案显着不同,其他人可能已经吸引ADM和功能解决方案之间的表面相似性是偶然的。

2)代码膨胀

我估计ADM中生成的代码量是OOP / RDM解决方案所创建的代码量的5-10倍。这可能占代码重复的50%,其中30%是锅炉板代码,20%是解决或解决由于缺乏RDM而引起的问题。

3)对域问题的理解不足

ADM和对域名问题的不了解有点牵手。出现了天真的解决方案,由于难以用现有的DM支持它们而很难考虑需求,并且鉴于开发时间较长且缺乏灵活性,ADM成为业务创新的重大障碍。

4)维护困难

需要一定程度的严格性以确保在所有表达的地方更改域概念,因为该概念可能不仅仅是复制和粘贴重新实现。这通常会导致在多个场合调查和修复相同的错误。

5)入职难度增加

我认为RDM的一个好处是概念的凝聚力,可以更快地理解域。由于ADM概念可能分散且缺乏清晰度,因此新开发人员难以获得。

我也很想将ADM的运营支持费用高于RDM,但这取决于很多因素。

正如其他人所指出的那样,请看DDD(Greg Evans,Vince Vaughn和Scott Millett)获取RDM的好处。

答案 12 :(得分:1)

它提供了更好的可预测性。这样的经理,特别是如果项目是付费的时间和材料。每一次改变都意味着很多工作,因此很多重复工作背后隐藏着艰巨的工作。在精心设计的DRY系统中,可预测性非常糟糕,因为您经常在做新事物。

答案 13 :(得分:0)

应该注意的是,随着系统的复杂性和变化粒度的增加,精心设计的消息传递对象模型所提供的接口点的封装和整合使得在不进行广泛重构的情况下更改和维护关键代码更加安全。 / p>

由ADM创建的服务层虽然更容易实现(因为它们需要相对较少的思考并且具有许多分散的接口点),但是当需要修改实时且不断增长的系统时,可能会产生麻烦。 / p>

我还可以补充一点,并非所有情况都要求域模型(更不用说ADM了)。有时最好使用更多过程/功能的任务风格,而不是依赖于应用程序范围的逻辑/业务规则。

如果您正在尝试确定整个应用程序的优缺点,我认为在您开始编写单行代码之前,首先要为您的应用程序设计每个应用程序的外观是很重要的。一旦你以两种风格对你的应用程序进行CRC校验或线框化,请退后一步,确定哪一个更有意义并更好地适应应用程序。

还要考虑哪一个更容易维护...

答案 14 :(得分:0)

我的团队个人更喜欢ADM。我们有一组业务对象代表我们域的特定部分。我们使用服务将这些对象保存到数据库。我们的业务对象确实有方法,但这些方法只能操纵它的内部状态。

使用ADM over RDM的好处可以从我们如何将对象持久化到db来看出。使用我们的遗留代码系统的开发人员可以使用我们的业务对象(来自新系统)并继续使用他们当前的数据访问层将这些对象持久保存到数据库。使用RDM会迫使遗留系统的开发人员将Repository对象注入我们的业务模型......这与他们当前的数据访问层不一致。

答案 15 :(得分:0)

为了扩展Michael的答案,我认为(相当)清楚该代码应该去哪里:进入一个专门的Mediator来处理Order和Inventory之间的交互。

从我的POV关于域名的关键是它必须保持简单的测试行为isInThisState()方法等。根据我的经验,这些也分散在大多数公司的服务热泪(sic :))中,并且复制了ar无休止地重写。所有这些都违反了标准的凝聚规则。

在我看来,这种方法应该是目标用于持有尽可能多的商业行为的DM,将其余部分放在明确指定的区域(即不在服务中)

答案 16 :(得分:-14)

anemic domain model是一种反模式。反模式没有专业。