对于这个主题我如果提供的内容重复了,那么请见谅。在相关问题中,我找不到任何关于这个主题的具体例子。
在阅读马丁·福勒(Martin Fowler)的有关“贫血对象模型”文章之后,我不明白为什么它被认为是一种反模式。因为据我所知,也许90%的j2ee应用程序都是以“贫血”的方式设计的,即使大多数企业开发人员认为它是一种反模式?
是否有人可以推荐更多有关此主题的阅读材料(除了“领域驱动设计”书籍),或者更好的是,给出关于如何以不良方式影响应用程序设计的具体示例来说明这种反模式。
谢谢
对于这个主题我如果提供的内容重复了,那么请见谅。在相关问题中,我找不到任何关于这个主题的具体例子。
在阅读马丁·福勒(Martin Fowler)的有关“贫血对象模型”文章之后,我不明白为什么它被认为是一种反模式。因为据我所知,也许90%的j2ee应用程序都是以“贫血”的方式设计的,即使大多数企业开发人员认为它是一种反模式?
是否有人可以推荐更多有关此主题的阅读材料(除了“领域驱动设计”书籍),或者更好的是,给出关于如何以不良方式影响应用程序设计的具体示例来说明这种反模式。
谢谢
马丁·福勒给这个行业带来了许多词汇,但理解却很少。
现今大部分应用程序(Web/数据库)确实需要许多对象来公开它们的属性。
任何自称权威的人士对此做法不以为然时,都应该以身作则,并向我们展示一个成功的真实应用程序,其中充满了他神奇原则的具体体现。
否则就请闭嘴。我们这个行业有太多的空话了。这是工程领域,不是戏剧俱乐部。
要获取完整答案,请查看我的博客,其中还包含源代码示例 [blog]: 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
不可变,则可以朝着函数式编程的方向发展。但这是另一个话题,我无法在此探讨。 Order.getTotal()
值就是不正确的。在实际应用中,找出谁添加了订单项以及为什么没有调用OrderService可能会很麻烦。正如您可能已经意识到的那样,Order还破坏了订单项列表的封装性。有人可以调用order.getItems().add(orderItem)
来添加订单项。这可能会使查找真正添加该项的代码变得困难(order.getItems()
引用可以通过整个应用程序传递)。OrderService
的calculateTotal
方法负责计算所有订单对象的总数。因此,它必须是无状态的。但是,无状态也意味着它不能缓存总值,并且仅在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<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;
}
}
丰富的领域模型尊重面向对象的原则,并保证其在任何时候都处于合法状态。
嗯,你说得对,几乎所有的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++出现时,结构体变成了类,并允许您将函数放入结构体(类)中。然后,结构体被隐式传递为this指针,因此您可以创建类并调用其方法,而不是创建结构体并将其传递给函数。这样编写的代码更清晰、更易于理解。
后来我转向了Java领域,人们将模型与服务分开,即模型是一种较高级别的结构体,而服务则是一组在模型上操作的函数。对我来说,这听起来很像一种C语言习惯用法。这很有趣,因为在C语言中这样做是因为该语言没有提供更好的替代方案,在Java中这样做是因为程序员不知道有更好的方案。
考虑以下两个类:
class CalculatorBean
{
//getters and setters
}
class CalculatorBeanService
{
Number calculate(Number first, Number second);
{
//do calculation
}
}
如果我理解正确的话,Fowler 的意思是因为你的 CalculatorBean
只是一堆 getter/setter,所以你不会从中获得任何真正的价值,如果你将该对象移植到另一个系统中,它将没有作用。问题似乎在于你的 CalculatorBeanService
包含了 CalculatorBean
应该负责的所有内容。这并不是最好的方案,因为现在 CalculatorBean
将所有的职责委托给了 CalculatorBeanService
。这简单违反了“告诉,别问”原则,即对象应该告诉客户端它们可以或不能做什么,而不是暴露属性并让客户端确定对象是否处于特定状态以执行某个操作。
和软件开发领域的大多数事情一样,没有黑白之分。有些情况下贫血领域模型是最合适的选择。
但是有很多情况下,开发人员试图构建一个领域模型,也就是进行DDD,结果却得到了一个贫血领域模型。我认为在这种情况下,贫血领域模型被认为是反模式。
只要确保使用最适合工作的工具,如果它对你有效,就不要费心去改变它。