有关完整的答案,请查看我的博客,其中还包含源代码示例[博客]:https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
如果你从面向对象的角度来看贫乏域模型,它绝对是一个反模式,因为它是纯粹的过程编程。它被称为反模式的原因是,主要的面向对象原则没有被贫乏领域模型所覆盖:
面向对象意味着:对象管理其状态并保证其在任何时候都处于合法状态。(数据隐藏、封装)
因此,对象封装数据并管理数据的访问和解释。与此相反,贫血模型并不能保证它在任何时候都处于合法状态。
包含订单项的订单示例将有助于显示差异。因此,让我们看一下订单的贫血模型。
贫血模型
public class Order {
private BigDecimal total = BigDecimal.ZERO;
private List<OrderItem> items = new ArrayList<OrderItem>();
public BigDecimal getTotal() {
return total;
}
public void setTotal(BigDecimal total) {
this.total = total;
}
public List<OrderItem> getItems() {
return items;
}
public void setItems(List<OrderItem> 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<OrderItem> 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
上述贫血阶模型的问题在于:
- 如果有人将 OrderItem 添加到订单中,则只要 OrderService 尚未重新计算该值,该值就不正确。在实际的应用程序中,找出谁添加了订单项以及为什么没有调用 OrderService 可能很麻烦。您可能已经认识到,订单还破坏了订单项列表的封装。有人可以打电话添加订单项。这可能会使找到真正添加项目的代码变得困难(引用可以通过整个应用程序传递)。
Order.getTotal()
order.getItems().add(orderItem)
order.getItems()
- 的方法负责计算所有 Order 对象的总数。因此,它必须是无状态的。但无状态也意味着它不能缓存总值,只有在 Order 对象更改时才重新计算它。因此,如果 computeTotal 方法需要很长时间,则还存在性能问题。然而,您将遇到性能问题,因为客户可能不知道订单是否处于合法状态,因此即使不需要,也可以预防性调用。
OrderService
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<OrderItem> 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<OrderItem> items = new ArrayList<OrderItem>();
/**
* 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<OrderItem> 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<OrderItem> 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;
}
}
富域模型尊重面向对象的原则,并保证它在任何时候都处于合法状态。
引用