完全な回答については、ソース コードの例を含む私のブログをご覧ください [ブログ]: 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 を Order に追加した場合
Order.getTotal()
、OrderService によって再計算されていない限り、値は正しくありません。実際のアプリケーションでは、誰が注文アイテムを追加したのか、なぜ OrderService が呼び出されなかったのかを調べるのは面倒です。既にお気づきかもしれませんが、Order は注文項目リストのカプセル化も破ります。誰かが電話order.getItems().add(orderItem)
して注文項目を追加できます。これにより、アイテムを実際に追加するコードを見つけるのが難しくorder.getItems()
なる可能性があります (参照はアプリケーション全体に渡されます)。
- のメソッドは、すべての Order オブジェクト
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;
}
}
リッチ ドメイン モデルは、オブジェクト指向の原則を尊重し、常に合法的な状態にあることを保証します。
参考文献