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

Ste*_*orn 76 language-agnostic design-patterns

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

福勒文章

Eri*_*c P 128

由于"贫血领域模型"是反模式,为什么有这么多系统实现这一点?

我认为有几个原因

1.系统的复杂性

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

将产品添加到订单

我把这个功能放在了订单上

public void Order.AddOrderLine(Product product)
{
    OrderLines.Add(new OrderLine(product));
}
Run Code Online (Sandbox Code Playgroud)

很好,超级面向对象.

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

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

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

    order.AddOrderLine(product);
}
Run Code Online (Sandbox Code Playgroud)

我也可以将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.

OOP很难,程序很容易

我对此有一点" 知识诅咒 ",但我发现对于拥有DTO和服务的新开发者来说比Rich Domain容易得多.

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

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

  • 总结 - 所有这些原因都是为什么它不是反模式.斯拉夫人对OO的忠诚是一种反模式.现在添加并发性而无需投影,并观察如何将自己与Fowler样式OO结合在一起.看看大规模并发的公司(例如google),看看他们正在做多少OO. (11认同)
  • 我没有看到IoC到Anemic的相关性.这似乎是一个完全不相关的问题.您的服务/存储库或域对象是否实现接口与测试它们无关.相反,您测试服务/存储库/域对象并将它们与它们自己实现接口的依赖项分离.无论你做得对不对,我都看不到与IoC的任何关联. (4认同)
  • 关于DDD我正在努力解决Grok的问题是你的第一点:"当系统不仅仅是基本的CRUD时,你最终会得到OrderService中的大部分逻辑,而订单中的逻辑很少." 这对我来说很有意义,但DDD的目的不是用于比这更复杂的系统吗? (2认同)

Ter*_*cox 36

专业人士:

  • 您可以声称它是一个域模型并吹嘘给您的开发人员朋友并将其放在简历上.
  • 从数据库表中自动生成很容易.
  • 它令人惊讶地映射到数据传输对象.

缺点:

  • 您的域逻辑存在于其他地方,可能在一个充满类(静态)方法的类中.或者您的GUI代码.或者在多个地方,都有相互冲突的逻辑.
  • 这是一种反模式,因此其他开发人员会问您是否理解面向对象设计的概念.

  • 否.贫血模型是否是反模式是一个意见问题.福勒(我尊重并且通常遵循的工作)说它是.我不同意(不是说我的话有任何重量),OO战壕中的许多人也不同意.纯OO建模通常不适用于所有情况,整个行业都从经验中知道.此外,贫血模型仍然可以遵守OO建模指南.因此,看到一个适用的特定情况并不会使人们对OO设计的理解产生疑问. (59认同)
  • 任何模式是否是反模式都是一个观点问题.强大的域模型是良好的OO设计,贫血域模型是糟糕的OO设计.因此,在OO的背景下,它是一种反模式.这并不意味着它不会被使用或在所有情况下都不合适,但从个人经验来看,我认为它比单身成瘾更糟糕. (12认同)
  • +1关于从数据库表生成代码的观点对某些人来说非常重要.如果您处于快速原型开发模式,则自动代码生成非常有用.我们并不假装这是适当的OO设计,并且以一种草率的方式将事物放在一起会产生"技术债务".然而,在许多项目中,上市时间是一个更高的优先级. (7认同)
  • 我觉得功能编程有一种趋势,而领域模型往往会产生很多副作用.我感觉贫穷的领域模型正在卷土重来,因为它允许更具功能性的编程风格,其中您的业务逻辑需要数据并返回已处理的数据.您的服务层通过了解哪个数据需要由哪个业务逻辑方法处理来监督所有数据流. (6认同)
  • @TerryWilcox嗯,我不是功能专家,但功能编程也有数据结构.贫血领域模型使用对象作为数据的容器,它们更像结构.我的观点是,使用ADM,您可以采用更具功能性的方式,在这种方式中,您拥有随时间转换为新的和新的状态的不可变数据.我可以看到这在Scala中完成,例如,试图利用一些OOP并应用一些FP.有点像他们在这里解释一下:http://www.slideshare.net/debasishg/qconny-12 (2认同)
  • @TerryWilcox 我在你的回答中没有看到任何关于为什么一个比另一个更好的论点。你只是发表了意见。-1 (2认同)

小智 20

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


小智 15

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

"贫血领域模式是一种反模式.反模式没有优势."

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

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

  • ...这似乎是有人试图用它的小节目来扮演魔鬼的adovcate.`它有可能仍有一些(虽然相对较小)的上涨.然后请说出一些!至少有一个,而不是假设! (6认同)
  • ... 哪个是?我不是想欺骗你或任何人,但我真的很好奇.这个缺点已被提及了一千次,但我仍然在寻找ADM的有弹性的优点(除了极端的短语). (2认同)
  • 更容易知道某些逻辑属于哪些非单类相关的任何事物(任何实际业务).正如在其他地方所说的那样,"富域模型"最终在多个位置具有逻辑(服务层,对象图中涉及的多个类,......).此外,RDM不会让您轻松查看完整的业务逻辑,也不会轻易避免循环等.没有逻辑的哑数据结构在OO应用程序中占有一席之地. (2认同)

joe*_*ely 13

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

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

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

  2. 开发人员致力于将服务式"面孔"放在以非OO方式设计的遗留系统上(无论语言如何).

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

对于理想上纯的OO系统必须与现有的非OO环境进行交互的边界,这种折衷可能很常见.


Lad*_*ein 8

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

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

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


小智 5

"开发人员正致力于将服务式"面对"用于以非OO方式设计的遗留系统(无论语言如何)."

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


Mr *_*phe 5

在与 ADM 一起使用“成熟”系统后,我觉得我至少可以针对这个问题提供一些轶事反馈。

1)缺乏封装

在具有 ADM 的实时系统中,可以编写例如 'obj.x = 100; obj.save',即使这违反了业务逻辑。这会导致许多错误,如果在对象上建模不变量则不会遇到这些错误。我认为这些错误的严重性和普遍性对 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 的好处。