Déversement d'informations à travers les limites des objets

10

Souvent, mes objets métier ont tendance à avoir des situations où les informations doivent traverser trop souvent les limites des objets. En faisant OO, nous voulons que les informations soient dans un objet et autant que possible tout le code traitant de ces informations devrait être dans cet objet. Cependant, les règles commerciales ne suivent pas ce principe, ce qui me pose problème.

À titre d'exemple, supposons que nous avons une commande qui a un certain nombre de OrderItems qui se réfère à un InventoryItem qui a un prix. J'appelle Order.GetTotal () qui résume le résultat de OrderItem.GetPrice () qui multiplie une quantité par InventoryItem.GetPrice (). Jusqu'ici tout va bien.

Mais ensuite, nous découvrons que certains articles sont vendus avec une offre deux pour un. Nous pouvons gérer cela en demandant à OrderItem.GetPrice () de faire quelque chose comme InventoryItem.GetPrice (quantité) et en laissant InventoryItem s'en occuper.

Cependant, nous découvrons ensuite que l'accord deux pour un ne dure que pour une période donnée. Cette période doit être basée sur la date de la commande. Maintenant, nous changeons OrderItem.GetPrice () pour être InventoryItem.GetPrice (quatity, order.GetDate ())

Mais ensuite, nous devons prendre en charge différents prix en fonction de la durée de vie du client dans le système: InventoryItem.GetPrice (quantité, order.GetDate (), order.GetCustomer ())

Mais il s'avère que les offres deux pour un s'appliquent non seulement à l'achat de plusieurs articles du même inventaire, mais à l'achat de plusieurs articles dans une catégorie d'inventaire. À ce stade, nous levons la main et donnons simplement à InventoryItem l'élément de commande et lui permettons de parcourir le graphique de référence de l'objet via des accesseurs pour obtenir les informations dont il a besoin: InventoryItem.GetPrice (this)

TL; DR Je veux avoir un faible couplage dans les objets, mais les règles métier m'obligent souvent à accéder à des informations de partout pour prendre des décisions particulières.

Existe-t-il de bonnes techniques pour y faire face? D'autres trouvent-ils le même problème?

Winston Ewert
la source
4
À première vue, il semble que la classe InventoryItem essaie d'en faire trop en calculant le prix, renvoyer un prix donné est une chose, mais les prix de vente et les règles commerciales spéciales ne doivent pas être traités dans InventoryItem. Déployez une classe pour calculer vos prix et laissez-la gérer le besoin de données de commande, d'inventaire et de client, etc.
Chris
@Chris, oui, diviser la logique de tarification est probablement une bonne idée. Mon problème est qu'un tel objet sera étroitement couplé à tous les objets liés à l'ordre. C'est la partie qui me dérange et la partie que je me demande si je peux éviter.
Winston Ewert
1
Si quelque chose, je ne veux pas l'appeler quoi que ce soit, a besoin de toutes ces informations, il doit en quelque sorte consommer ces informations pour produire le résultat souhaité. Si vous avez déjà un inventaire, un article, un client, etc., la mise en œuvre de la logique métier dans son propre domaine est la plus logique. Je n'ai pas vraiment de meilleure solution.
Chris

Réponses:

6

Nous avons essentiellement eu la même expérience où je travaille et nous l'avons résolue en ayant une classe OrderBusinessLogic. Pour la plupart, la disposition que vous avez décrite fonctionne pour la majorité de nos activités. C'est agréable et propre et simple. Mais dans les cas où vous en avez acheté 2 dans cette catégorie, nous traitons cela comme une "exécution commerciale" et la classe OrderBL recalcule les totaux en parcourant les objets nécessaires.

Est-ce une solution parfaite, non. Nous avons encore une classe qui en sait beaucoup trop sur les autres classes, mais au moins nous avons déplacé ce besoin hors des objets métier et dans une classe de logique métier.

Walter
la source
2
En cas de doute, ajoutez une autre classe.
Chris
1

On dirait que vous avez besoin d'un objet Discount séparé (ou d'une liste d'entre eux) qui garde une trace de tout cela, puis appliquez les rabais à la commande, quelque chose comme Order.getTotal(Discount)ou Discount.applyTo(Order)ou similaire.

John Bode
la source
Je peux voir où mettre le code dans une remise plutôt que dans l'objet d'inventaire serait mieux. Mais l'objet de réduction, semble-t-il, devrait toujours accéder aux informations de tous les différents objets pour faire son travail. Donc, cela ne résout pas, du moins autant que je sache, le problème.
Winston Ewert
2
Je pense que l'objet Discount est une bonne idée, mais je le ferais implémenter un modèle d'observateur ( en.wikipedia.org/wiki/Observer_pattern ), avec les remises comme sujet (s) du modèle
BlackICE
1

Il est parfaitement correct d'accéder aux données d'autres classes. Cependant, vous voulez que ce soit une relation unidirectionnelle. Par exemple, supposons que ClassOrder accède à CallItem. Idéalement, ClassItem ne devrait pas accéder à ClassOrder. Ce que je pense que vous manquez dans votre classe de commande est une sorte de logique commerciale qui peut ou non justifier une classe telle que celle suggérée par Walter.

Edit: Réponse au commentaire de Winston

Je ne pense pas que vous ayez besoin d'un objet d'objet d'inventaire ... du moins de la manière dont vous l'utilisez. J'aurais plutôt une classe d'inventaire qui gérait une base de données d'inventaire.

Je me référerais aux articles d'inventaire par un ID. Chaque commande contiendrait une liste d'ID d'inventaire et une quantité correspondante.

Ensuite, je calculerais le total de la commande avec quelque chose comme ça.
Inventory.GetCost (Items, customerName, date)

Ensuite, vous pourriez avoir une autre fonction d'aide comme:

Inventory.ItemsLefts (int itemID)
Inventory.AddItem (int itemID, int quantity)
Inventory.RemoveItem (int itemID, int quantity)
Inventory.AddBusinessRule (...)
Inventory.DeleteBusinessRule (...)

Pemdas
la source
C'est parfaitement bien de demander des données à des classes étroitement liées. Le problème se pose lorsque j'accède aux données de classes indirectement liées. Pour implémenter cette logique dans la classe Order, il faudrait que la classe Order traite directement avec l'objet Inventory (qui contient les données de tarification nécessaires). C'est la partie qui me dérange car elle associe l'Ordre à des classes dont (idéalement) elle ne saurait rien.
Winston Ewert
Essentiellement, votre idée serait d'éliminer OrderItem à la place, Order garde la trace de toutes ces informations elles-mêmes. Il est ensuite facile de transmettre ces informations à un autre objet pour obtenir des informations sur les prix. Ça pourrait marcher. (Dans le scénario particulier, cet exemple est vaguement basé sur le OrderItem était trop compliqué et devait vraiment être son propre objet)
Winston Ewert
Ce qui n'a pas de sens pour moi, c'est pourquoi vous voudriez que l'inventaire soit référencé en utilisant des numéros d'identification plutôt que des références d'objet. Il me semble bien préférable de garder une trace des références et des quantités d'objets, puis des identifiants et références des articles.
Winston Ewert
Je ne suggère pas du tout cela. Je pense que vous devriez toujours avoir une classe ou une structure pour les articles de commande. Je ne pense tout simplement pas que les objets individuels de l'inventaire devraient avoir le prix à l'intérieur, principalement parce que je ne sais pas comment vous pourriez les obtenir sans demander une sorte de base de données. La commande ou l'article en stock serait strictement une classe de données contenant un identifiant d'article et une quantité. L'ordre lui-même aurait une liste de ces objets.
Pemdas
Je ne sais pas vraiment ce que vous demandez dans le deuxième commentaire. Tout ne doit pas nécessairement être un objet. Je ne suis vraiment pas sûr de savoir comment localiser un élément spécifique sans une sorte d'identifiant.
Pemdas
0

Après y avoir réfléchi davantage, j'ai trouvé ma propre stratégie alternative.

Définissez une classe de prix. Inventory.GetPrice()renvoie un objet prix

Price OrderItem::GetPrice()
{
    return inventory.GetPrice().quantity(quantity);
}

Currency OrderItem::GetTotal()
{
    Price price = empty_price();
    for(item in order_items)
    {
         price = price.combine( item.GetPrice() )
    }
    price = price.date(date).customer(customer);
    return price.GetActualPrice();
}

Maintenant, la classe de prix (et éventuellement certaines classes connexes) résume la logique de tarification et l'ordre n'a pas à s'en soucier. Price ne sait rien de Order / OrderItem au lieu d'avoir simplement des informations.

Winston Ewert
la source