Exemples concrets expliquant pourquoi le "modèle du domaine anémique" est considéré comme un modèle anti-]
Je m'excuse si c'est un duplicata, mais je n'ai pas pu trouver d'exemples concrets sur le sujet dans les questions connexes.
Après la lecture de Martin Fowler article sur le Anémique Modèle de Domaine', je me demande pourquoi cela est considéré comme un modèle anti -. Même la majorité des développeurs d'entreprise considèrent-ils QU'il s'agit d'un modèle anti-car AFAIK probablement 90% des applications j2ee sont conçues d'une manière "anémique"?
quelqu'un Peut-il recommander la lecture sur le sujet (autre que le livre "Domain Driven Design"), ou encore mieux, donner des exemples concrets sur la façon dont cet anti-modèle affecte la conception de l'application d'une mauvaise manière.
Merci,
6 réponses
étant donné les deux classes suivantes:
class CalculatorBean
{
//getters and setters
}
class CalculatorBeanService
{
Number calculate(Number first, Number second);
{
//do calculation
}
}
si je comprends bien, Fowler déclare que parce que votre CalculatorBean
est juste un tas de getters/setters vous ne gagnez pas de valeur réelle de celui-ci et si vous portez cet objet à un autre système il ne fera rien. Le problème semble que votre CalculatorBeanService
contient tout ce que l' CalculatorBean
devrait être responsable. Ce qui n'est pas le meilleur que maintenant, le CalculatorBean
délègue toute sa responsabilité à la CalculatorBeanService
pour la réponse complète jetez un oeil à mon blog qui contient aussi des exemples de code source [blog]: https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
si vous regardez le modèle de domaine anémique d'un point de vue orienté objet, c'est certainement un modèle anti-motif parce que c'est une programmation purement procédurale. La raison pour laquelle il est appelé un anti-modèle est que le principe principal orienté objet n'est pas couvert par un anémique modèle de domaine:
orientée Objet: un objet gère son état et garantit qu'il est dans un etat de droit, à tout moment. (données de la clandestinité, encapsulation)
donc un objet encapsule des données et gère l'accès et l'interprétation de celles-ci. En revanche, un modèle anémique ne garantit pas qu'il est à tout moment dans un État de droit.
Un exemple d'une commande avec les éléments de commande aidera à montrer la différence. Donc, nous allons jetez un oeil à une anémie modèle d'une commande.
anémique modèle
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;
}
}
alors où se trouve la logique qui interprète les éléments de la commande et de la commande pour calculer le total de la commande? Cette logique est souvent placée dans des classes nommées *Helper, *Util, *Manager ou tout simplement *Service. Un service de commande dans un modèle anémique ressemblerait à ceci:
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);
}
}
dans un modèle anémique, vous invoquez une méthode et lui passez le modèle anémique pour amener le modèle anémique à un état juridique. Par conséquent, la gestion de l'état du modèle anémique est placée en dehors du modèle anémique et ce fait en fait un anti-modèle d'une perspective orientée objet.
parfois, vous verrez une implémentation de service légèrement différente qui ne modifie pas le modèle anémique. Il renvoie plutôt la valeur qu'il calcule. E. g.
public BigDecimal calculateTotal(Order order);
Dans ce cas, le Order
n'est pas une propriété total
. Si vous faites maintenant le Order
immuables vous êtes sur le chemin de la fonctionnelle de programmation. Mais c'est un autre sujet que je ne peux pas découvrir ici.
Les problèmes avec la faiblesse de la commande le modèle ci-dessus sont:
- si quelqu'un ajoute un OrderItem à L'ordre le
Order.getTotal()
la valeur est incorrecte tant qu'elle n'a pas été recalculée par le service de commande. Dans une application du monde réel, il peut être difficile de savoir qui a ajouté l'article de commande et pourquoi le service de commande n'a pas été appelé. Comme vous avez pu déjà reconnu l'Ordre des pauses encapsulation de la liste des articles de commande. Quelqu'un peut appelerorder.getItems().add(orderItem)
pour ajouter un poste de commande. Cela peut rendre difficile de trouver le code qui ajoute vraiment l'élément (order.getItems()
référence peut être transmise à travers toute l'application). OrderService
calculateTotal
méthode est responsable du calcul du total pour tous les objets d'Ordre. Elle doit donc être apatride. Mais stateless signifie aussi qu'il ne peut pas mettre en cache la valeur totale et ne peut la recalculer que si l'objet Order a changé. Donc, si l' méthode calculateTotal prend beaucoup de temps vous avez également un problème de performance. Néanmoins, vous aurez des problèmes de performance, parce que les clients pourraient ne pas savoir si L'ordre est dans un état juridique ou non et donc prévenir appelcalculateTotal(..)
même quand il n'est pas nécessaire.
vous verrez aussi parfois que les services ne mettent pas à jour le modèle anémique et renvoient simplement le résultat. E. g.
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;
}
}
dans ce cas, les services interprètent l'état de l'anémie ne mettez pas à jour le modèle anémique avec le résultat. Le seul avantage de cette approche est que le modèle anémique ne peut pas contenir untotal
état, parce qu'il n'aura pas un total
propriété. Mais cela signifie aussi que l' total
doit être calculé chaque fois que cela est nécessaire. En supprimant l' total
propriété que vous conduisez les développeurs à utiliser le service et à ne pas compter sur le total
propriété de l'état. Mais cela ne garantit pas que les développeurs de cache total
valeur d'une certaine manière et donc ils pourraient également utiliser des valeurs qui sont dépassées. Cette façon de mettre en œuvre un service peut être faite chaque fois qu'une propriété est dérivée d'une autre propriété. Ou en d'autres termes... lorsque vous interprétez des données de base. E. g. int getAge(Date birthday)
.
Maintenant, regardez le modèle rich domain pour voir la différence.
La richesse approche de domaine
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;
}
}
le modèle du domaine riche respecte les principes orientés objet et garantit qu'il est dans un état juridique à n'importe quel moment.
Références
Martin Fowler apporte à cette industrie beaucoup de mots et moins de compréhension.
la majorité des applications aujourd'hui (web/db) ont besoin de nombreux objets qui exposent leurs propriétés.
N'importe quelle autorité (autoproclamée) froncant les sourcils sur une telle pratique devrait conduire par l'exemple, et nous montrer une application réussie du monde réel qui est pleine d'incarnations de ses principes merveilleux.
Ou bien se taire. C'est écœurant qu'il y ait tant de chaleurs dans notre industrie. C'est génie, pas un club de théâtre.
Bien. Vous avez raison, presque tout le code java est écrit de cette façon. La raison pour laquelle il s'agit d'un modèle anti est que l'un des principaux principes de la conception orientée objet est de combiner les données et les fonctions qui fonctionnent sur elle en un seul objet. Par exemple, lorsque j'écrivais du code c de l'ancienne école, nous imitions le design orienté objet comme ceci:
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
}
ce qui veut dire que le langage ne nous a pas permis de combiner les fonctions pour fonctionner sur SomeStruct à l'intérieur de la structure, donc nous avons créé un groupe de fonctions libres qui par convention ont pris SomeStruct comme un premier param.
quand c++ est arrivé, la struct est devenue une classe, et elle vous permet de mettre des fonctions dans la struct (class). Alors la struct est implicitement passée comme le pointeur ci-dessus, donc au lieu de créer une struct et de la passer aux fonctions, vous créez la classe et les méthodes d'appel contre elle. Le code est plus clair et plus facile à comprendre de cette façon.
Puis j'ai déménagé dans le monde java, et tout le monde sépare le modèle du service, ce qui veut dire que le modèle est une structure glorifiée, et le service, étant apatride comme il est, devient un ensemble de fonctions qui opère sur un modèle. Qui, pour moi, ressemble étrangement à un langage c langage. C'est assez drôle parce qu'en c, c'était fait parce que le langage n'offrait rien de mieux, et en java, c'est fait parce que les programmeurs ne savent pas faire mieux.
comme pour la plupart des choses dans le monde du développement de logiciels, il n'y a pas de noir et blanc. Il y a des cas où un modèle de domaine anémique convient parfaitement.
mais il y a beaucoup de cas où les développeurs essaient de construire un modèle de domaine, alias do DDD, et finissent avec un mode de domaine anémique à la place. Je pense que dans ce cas le modèle de domaine anémique est considéré comme un anti-patern.
assurez-vous d'utiliser le meilleur outil pour le travail et si cela fonctionne pour vous n'avez pas pris la peine de changer il.
il viole simplement le "Dire, Ne Demandez pas" principe qui stipule que les objets doivent indiquer au client ce qu'ils peuvent ou ne peuvent pas faire plutôt que d'exposer les propriétés et de laisser au client le soin de déterminer si un objet est dans un état particulier pour qu'une action donnée ait lieu.