Über Objektgrenzen hinweg verschüttete Informationen

10

Oft haben meine Geschäftsobjekte Situationen, in denen Informationen zu oft Objektgrenzen überschreiten müssen. Wenn Sie OO ausführen, möchten wir, dass sich Informationen in einem Objekt befinden und dass sich der gesamte Code, der sich mit diesen Informationen befasst, so weit wie möglich in diesem Objekt befindet. Die Geschäftsregeln folgen jedoch nicht diesem Prinzip, was mir Probleme bereitet.

Nehmen wir als Beispiel an, wir haben eine Bestellung mit einer Anzahl von OrderItems, die sich auf ein InventoryItem mit einem Preis bezieht. Ich rufe Order.GetTotal () auf, das das Ergebnis von OrderItem.GetPrice () summiert, das eine Menge mit InventoryItem.GetPrice () multipliziert. So weit, ist es gut.

Aber dann stellen wir fest, dass einige Artikel mit zwei für einen Deal verkauft werden. Wir können dies bewältigen, indem OrderItem.GetPrice () so etwas wie InventoryItem.GetPrice (Menge) ausführt und InventoryItem damit umgehen lässt.

Dann stellen wir jedoch fest, dass der Zwei-für-Eins-Deal nur für einen bestimmten Zeitraum gilt. Dieser Zeitraum muss auf dem Datum der Bestellung basieren. Jetzt ändern wir OrderItem.GetPrice () in InventoryItem.GetPrice (quatity, order.GetDate ())

Aber dann müssen wir unterschiedliche Preise unterstützen, je nachdem, wie lange der Kunde im System ist: InventoryItem.GetPrice (Menge, Bestellung.GetDate (), Bestellung.GetCustomer ())

Es stellt sich jedoch heraus, dass die Zwei-für-Eins-Angebote nicht nur für den Kauf mehrerer Artikel desselben Inventargegenstands gelten, sondern für mehrere Artikel in einer Inventarkategorie. An diesem Punkt erheben wir unsere Hände und geben dem InventoryItem einfach die Bestellposition und lassen es über Accessoren über das Objektreferenzdiagramm fahren, um die benötigten Informationen zu erhalten: InventoryItem.GetPrice (this)

TL; DR Ich möchte eine geringe Kopplung in Objekten haben, aber Geschäftsregeln zwingen mich oft dazu, von überall auf Informationen zuzugreifen, um bestimmte Entscheidungen zu treffen.

Gibt es gute Techniken, um damit umzugehen? Finden andere das gleiche Problem?

Winston Ewert
quelle
4
Auf den ersten Blick scheint es so zu sein, dass die InventoryItem-Klasse versucht, durch Berechnung des Preises zu viel zu tun. Die Rückgabe eines bestimmten Preises ist eine Sache, aber Verkaufspreise und spezielle Geschäftsregeln sollten keine Adressen in InventoryItem sein. Führen Sie eine Klasse zur Berechnung Ihrer Preise ein und lassen Sie sie den Bedarf an Daten aus Bestellung, Inventar und Kunden usw. decken.
Chris
@ Chris, ja, die Preislogik aufzuteilen ist wahrscheinlich eine gute Idee. Mein Problem ist, dass ein solches Objekt eng mit allen auftragsbezogenen Objekten verbunden ist. Das ist der Teil, der mich stört und der Teil, den ich frage, ob ich es vermeiden kann.
Winston Ewert
1
Wenn etwas, das ich nicht nennen möchte, all diese Informationen benötigt, muss es diese Informationen irgendwie verbrauchen, um das gewünschte Ergebnis zu erzielen. Wenn Sie bereits Inventar, Artikel, Kunden usw. erstellt haben, ist die Implementierung der Geschäftslogik in einem eigenen Bereich am sinnvollsten. Ich habe keine bessere Lösung.
Chris

Antworten:

6

Wir haben im Grunde die gleiche Erfahrung gemacht, in der ich arbeite, und wir haben sie mit einer OrderBusinessLogic-Klasse angegangen. Das von Ihnen beschriebene Layout funktioniert größtenteils für den Großteil unseres Geschäfts. Es ist schön und sauber und einfach. In den Fällen, in denen Sie 2 aus dieser Kategorie gekauft haben, behandeln wir dies als "Geschäftsausführung" und lassen die OrderBL-Klasse die Gesamtsummen neu berechnen, indem sie die benötigten Objekte durchlaufen.

Ist es eine perfekte Lösung, nein. Wir haben immer noch eine Klasse, die viel zu viel über die anderen Klassen weiß, aber zumindest haben wir dieses Bedürfnis aus den Geschäftsobjekten in eine Geschäftslogikklasse verschoben.

Walter
quelle
2
Wenn Sie Zweifel haben, fügen Sie eine weitere Klasse hinzu.
Chris
1

Klingt wie Sie benötigen ein eigenes Discount - Objekt (oder eine Liste von ihnen) , die den Überblick über all das Zeug hält, und setzt dann den Rabatt (n) an den Orden, so etwas wie Order.getTotal(Discount)oder Discount.applyTo(Order)oder ähnliches.

John Bode
quelle
Ich kann sehen, wo es besser wäre, den Code in einen Rabatt anstatt in das Inventarobjekt einzufügen. Es scheint jedoch, dass das Rabattobjekt immer noch auf Informationen von allen verschiedenen Objekten zugreifen muss, um seine Arbeit zu erledigen. Das löst das Problem also nicht, zumindest soweit ich sehen kann.
Winston Ewert
2
Ich denke, das Discount-Objekt ist eine gute Idee, aber ich würde es ein Beobachtermuster ( en.wikipedia.org/wiki/Observer_pattern ) implementieren lassen , mit den Rabatten als Betreff (e) des Musters
BlackICE
1

Es ist vollkommen in Ordnung, auf Daten aus anderen Klassen zuzugreifen. Sie möchten jedoch, dass dies eine einseitige Beziehung ist. Angenommen, ClassOrder greift auf CallItem zu. Im Idealfall sollte ClassItem nicht auf ClassOrder zugreifen. Was ich denke, dass Sie in Ihrer Auftragsklasse fehlen, ist eine Art Geschäftslogik, die eine Klasse wie die von Walter vorgeschlagene rechtfertigen kann oder nicht.

Bearbeiten: Antwort auf Winstons Kommentar

Ich glaube nicht, dass Sie überhaupt ein Inventargegenstandsobjekt benötigen ... zumindest so, wie Sie es verwenden. Ich hätte stattdessen eine Inventarklasse, die eine Inventardatenbank verwaltet.

Ich würde auf Inventargegenstände durch eine ID verweisen. Jede Bestellung würde eine Liste von Inventar-IDs und eine entsprechende Menge enthalten.

Dann würde ich die Summe der Bestellungen mit so etwas berechnen.
Inventory.GetCost (Artikel, Kundenname, Datum)

Dann könnten Sie andere Hilfsfunktionen haben wie:

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

Pemdas
quelle
Es ist vollkommen in Ordnung, Daten von eng verwandten Klassen anzufordern. Das Problem tritt auf, wenn ich auf Daten aus indirekt verwandten Klassen zugreife. Um diese Logik in der Order-Klasse zu implementieren, muss sich die Order-Klasse direkt mit dem Inventory-Objekt befassen (das die erforderlichen Preisdaten enthält). Das ist der Teil, der mich stört, weil er Ordnung an Klassen koppelt, von denen er (im Idealfall) nichts wissen würde.
Winston Ewert
Im Wesentlichen besteht Ihre Idee darin, OrderItem zu entfernen. Stattdessen verfolgt Order alle diese Informationen selbst. Es ist dann einfach, diese Informationen zur Preisinformation an ein anderes Objekt weiterzugeben. Das könnte funktionieren. (In der jeweiligen Szenerie basiert dieses Beispiel lose auf dem OrderItem, das zu kompliziert war und wirklich sein eigenes Objekt sein musste)
Winston Ewert
Was für mich keinen Sinn ergibt, ist, warum Sie möchten, dass Inventar mit ID-Nummern und nicht mit Objektreferenzen referenziert wird. Es scheint mir viel besser, Objektreferenzen und -mengen im Auge zu behalten als Artikel-IDs und Referenzen.
Winston Ewert
Ich schlage das überhaupt nicht wirklich vor. Ich denke, dass Sie noch eine Klasse oder Struktur für Bestellartikel haben sollten. Ich denke nur nicht, dass die einzelnen Inventargegenstandsobjekte den Preis enthalten sollten, hauptsächlich, weil ich nicht sicher bin, wie Sie sie dort hineinbringen würden, ohne eine Art Datenbank zu fragen. Die Bestell- oder Inventarposition wäre streng genommen eine Datenklasse, die eine Artikelkennung und eine Menge enthält. Die Bestellung selbst hätte eine Liste dieser Objekte.
Pemdas
Ich bin mir nicht sicher, was Sie im zweiten Kommentar fragen. Nicht alles muss ein Objekt sein. Ich bin mir wirklich nicht sicher, wie Sie einen bestimmten Artikel ohne eine ID finden würden.
Pemdas
0

Nachdem ich mehr darüber nachgedacht habe, habe ich mir eine eigene alternative Strategie ausgedacht.

Definieren Sie eine Preisklasse. Inventory.GetPrice()gibt ein Preisobjekt zurück

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();
}

Jetzt verkapselt die Preisklasse (und möglicherweise einige verwandte Klassen) die Logik der Preisgestaltung, und die Bestellung muss sich nicht darum kümmern. Price weiß nichts über Order / OrderItem, stattdessen werden einfach Informationen eingegeben.

Winston Ewert
quelle