关于“贫血领域模型”被认为是反模式的具体例子

如果这是重复,我道歉,但我在相关问题中找不到关于该主题的任何具体示例。

在阅读了Martin Fowler关于“贫血领域模型”的文章之后,我不知道为什么这被认为是一种反模式。 大多数企业开发人员甚至认为它是一种反模式,因为AFAIK可能有90%的j2ee应用程序是以“贫血”方式设计的?

有人可以推荐进一步阅读这个主题(除了“领域驱动设计”一书),甚至更好,给出一个具体的例子,说明这种反模式如何以一种糟糕的方式影响应用程序设计。

谢谢,

给出以下两个类:

class CalculatorBean { //getters and setters } class CalculatorBeanService { Number calculate(Number first, Number second); { //do calculation } } 

如果我理解正确,Fowler声明,因为你的CalculatorBean只是一堆getter / setter,你没有从中获得任何真正的价值,如果你将该对象移植到另一个系统,它将什么都不做。 问题似乎是您的CalculatorBeanService包含CalculatorBean应负责的所有内容。 这不是最好的,因为现在CalculatorBean将其所有职责委托给CalculatorBeanService

要获得完整的答案,请查看我的博客,其中还包含源代码示例[博客]: https : //www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

如果从面向对象的角度来看贫血领域模型,它绝对是一种反模式,因为它是纯粹的程序编程。 它被称为反模式的原因是主要的面向对象原则不属于贫血领域模型:

面向对象意味着:对象管理其状态并保证它在任何时候都处于合法状态。 (数据隐藏,封装)

因此,对象封装数据并管理数据的访问和解释。 与此相反,贫血模型并不保证其在任何时候都处于合法状态。

订单商品订单的示例将有助于显示差异。 那么让我们来看看订单的贫血模型。

贫血模型

  public class Order { private BigDecimal total = BigDecimal.ZERO; private List items = new ArrayList(); public BigDecimal getTotal() { return total; } public void setTotal(BigDecimal total) { this.total = total; } public List getItems() { return items; } public void setItems(List items) { this.items = items; } } public class OrderItem { private BigDecimal price = BigDecimal.ZERO; private int quantity; private String name; public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } } 

那么逻辑位于何处解释订单和订单项以计算订单总额? 此逻辑通常放在名为* Helper,* Util,* Manager或简单* Service的类中。 贫血模型中的订单服务如下所示:

 public class OrderService { public void calculateTotal(Order order) { if (order == null) { throw new IllegalArgumentException("order must not be null"); } BigDecimal total = BigDecimal.ZERO; List items = order.getItems(); for (OrderItem orderItem : items) { int quantity = orderItem.getQuantity(); BigDecimal price = orderItem.getPrice(); BigDecimal itemTotal = price.multiply(new BigDecimal(quantity)); total = total.add(itemTotal); } order.setTotal(total); } } 

在贫血模型中,您调用一种方法并将其传递给贫血模型,以使贫血模型处于合法状态。 因此,贫血模型的状态管理被放置在贫血模型之外,这一事实使得它从面向对象的角度来看是一种反模式。

有时你会看到一个稍微不同的服务实现,不会修改贫血模型。 相反,它返回它计算的值。 例如

 public BigDecimal calculateTotal(Order order); 

在这种情况下, Order没有财产total 。 如果您现在使Order不可变,那么您正在进行函数式编程。 但这是我在这里找不到的另一个话题。

以上贫血订单模型存在的问题是:

  • 如果有人向Order添加OrderItem,则Order.getTotal()值不正确,只要它没有被OrderService重新计算。 在现实世界的应用程序中,找出添加订单商品的人以及未调用OrderService的原因可能很麻烦。 正如您可能已经认识到的那样,订单也会破坏订单商品列表的封装。 有人可以调用order.getItems().add(orderItem)来添加订单商品。 这可能使得很难找到真正添加项的代码( order.getItems()引用可以通过整个应用程序传递)。
  • OrderServicecalculateTotal方法负责计算所有Order对象的总和。 因此它必须是无国籍的。 但无状态也意味着它无法缓存总值,只有在Order对象发生更改时才重新计算它。 因此,如果calculateTotal方法需要很长时间,那么您也会遇到性能问题。 然而,您将遇到性能问题,因为客户可能不知道订单是否处于合法状态,因此即使不需要也可以预防性地调用calculateTotal(..)

您有时也会看到服务不会更新贫血模型,而只是返回结果。 例如

 public class OrderService { public BigDecimal calculateTotal(Order order) { if (order == null) { throw new IllegalArgumentException("order must not be null"); } BigDecimal total = BigDecimal.ZERO; List items = order.getItems(); for (OrderItem orderItem : items) { int quantity = orderItem.getQuantity(); BigDecimal price = orderItem.getPrice(); BigDecimal itemTotal = price.multiply(new BigDecimal(quantity)); total = total.add(itemTotal); } return total; } } 

在这种情况下,服务在某个时间解释贫血模型的状态,并且不会用结果更新贫血模型。 这种方法的唯一好处是贫血模型不能包含无效的total状态,因为它不具有total属性。 但这也意味着必须在每次需要时计算total 。 通过删除total属性,您可以引导开发人员使用该服务,而不是依赖于total的属性状态。 但这并不能保证开发人员以某种方式缓存total ,因此他们也可能使用过时的值。 只要从另一个属性派生属性,就可以实现这种实现服务的方式。 或者换句话说……当您解释基本数据时。 例如int getAge(Date birthday)

现在来看看富域模型,看看它们之间的区别。

丰富的域名方法

 public class Order { private BigDecimal total; private List items = new ArrayList(); /** * The total is defined as the sum of all {@link OrderItem#getTotal()}. * * @return the total of this {@link Order}. */ public BigDecimal getTotal() { if (total == null) { /* * we have to calculate the total and remember the result */ BigDecimal orderItemTotal = BigDecimal.ZERO; List items = getItems(); for (OrderItem orderItem : items) { BigDecimal itemTotal = orderItem.getTotal(); /* * add the total of an OrderItem to our total. */ orderItemTotal = orderItemTotal.add(itemTotal); } this.total = orderItemTotal; } return total; } /** * Adds the {@link OrderItem} to this {@link Order}. * * @param orderItem * the {@link OrderItem} to add. Must not be null. */ public void addItem(OrderItem orderItem) { if (orderItem == null) { throw new IllegalArgumentException("orderItem must not be null"); } if (this.items.add(orderItem)) { /* * the list of order items changed so we reset the total field to * let getTotal re-calculate the total. */ this.total = null; } } /** * * @return the {@link OrderItem} that belong to this {@link Order}. Clients * may not modify the returned {@link List}. Use * {@link #addItem(OrderItem)} instead. */ public List getItems() { /* * we wrap our items to prevent clients from manipulating our internal * state. */ return Collections.unmodifiableList(items); } } public class OrderItem { private BigDecimal price; private int quantity; private String name = "no name"; public OrderItem(BigDecimal price, int quantity, String name) { if (price == null) { throw new IllegalArgumentException("price must not be null"); } if (name == null) { throw new IllegalArgumentException("name must not be null"); } if (price.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException( "price must be a positive big decimal"); } if (quantity < 1) { throw new IllegalArgumentException("quantity must be 1 or greater"); } this.price = price; this.quantity = quantity; this.name = name; } public BigDecimal getPrice() { return price; } public int getQuantity() { return quantity; } public String getName() { return name; } /** * The total is defined as the {@link #getPrice()} multiplied with the * {@link #getQuantity()}. * * @return */ public BigDecimal getTotal() { int quantity = getQuantity(); BigDecimal price = getPrice(); BigDecimal total = price.multiply(new BigDecimal(quantity)); return total; } } 

富域模型尊重面向对象的原则,并保证它在任何时候都处于合法状态。

参考

  • 博客: https : //www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
  • 来源: https : //github.com/link-intersystems/blog/tree/master/anemic-vs-rich-domain-model

Martin Fowler为这个行业带来了许多话语和不太了解。

今天大多数应用程序(web / db)确实需要许多暴露其属性的对象。

任何对这种做法不屑一顾的权威(自称)都应以身作则,并向我们展示一个成功的现实世界应用,其中充满了他奇妙原则的体现。

或者闭嘴。 令人作呕的是,我们这个行业有如此多的热门话题。 这是工程,而不是戏剧俱乐部。

好。 你是对的,几乎所有的java代码都是用这种方式编写的。 它是反模式的原因是面向对象设计的主要原则之一是将数据和对其进行操作的函数组合成单个对象。 例如,当我编写旧的c代码时,我们将模仿面向对象的设计,如下所示:

 struct SomeStruct { int x; float y; }; void some_op_i(SomeStruct* s, int x) { // do something } void some_op_f(SomeStruct* s, float y) { // something else } 

也就是说,语言不允许我们在结构内部组合函数来操作SomeStruct,因此我们创建了一组自由函数,按惯例将SomeStruct作为第一个参数。

当c ++出现时,struct变成了一个类,它允许你将函数放入struct(class)中。 然后将结构隐式地作为this指针传递,因此不是创建结构并将其传递给函数,而是创建类并对其调用方法。 代码更清晰,更容易理解。

然后我转移到java世界,每个人都将模型与服务分开,也就是说模型是一个美化的结构,而服务是无状态的,它变成了一个在模型上运行的函数集合。 对我来说,听起来很像ac语言成语。 这很有趣,因为在c中它完成是因为语言没有提供更好的东西,而在Java中它已经完成了,因为程序员不知道更好。

与软件开发领域的大多数事情一样,没有黑色和白色。 有些情况下,贫血领域模型非常适合。

但是在很多情况下,开发人员尝试构建域模型,也就是做DDD,而最终导致贫穷的域模式。 我认为在这种情况下,贫血领域模型被认为是反模式。

只要确保你使用最好的工具,如果它适用于你,不要费心去改变它。

它只是违反了“Tell,Do not Ask”原则,该原则规定对象应该告诉客户端他们可以做什么或不做什么,而不是暴露属性并将其留给客户端以确定对象是否处于特定状态采取行动。