Rozlewanie informacji przez granice obiektów

10

Często moje obiekty biznesowe mają sytuacje, w których informacje zbyt często przekraczają granice obiektów. Kiedy robimy OO, chcemy, aby informacje były w jednym obiekcie i w miarę możliwości cały kod zajmujący się tymi informacjami powinien znajdować się w tym obiekcie. Jednak reguły biznesowe nie są zgodne z tą zasadą, co sprawia mi kłopoty.

Jako przykład załóżmy, że mamy Zamówienie, które ma wiele OrderItems, które odnoszą się do InventoryItem, który ma cenę. Wywołuję Order.GetTotal (), który sumuje wynik OrderItem.GetPrice (), który zwielokrotnia ilość przez InventoryItem.GetPrice (). Na razie w porządku.

Ale potem dowiadujemy się, że niektóre przedmioty są sprzedawane za dwie za jedną ofertę. Możemy sobie z tym poradzić, każąc OrderItem.GetPrice () zrobić coś takiego jak InventoryItem.GetPrice (ilość) i pozwalając InventoryItem sobie z tym poradzić.

Potem jednak dowiadujemy się, że umowa typu „dwa za jednego” trwa tylko przez określony czas. Ten okres musi być oparty na dacie zamówienia. Teraz zmieniamy OrderItem.GetPrice () na InventoryItem.GetPrice (quatity, order.GetDate ())

Ale potem musimy obsługiwać różne ceny w zależności od tego, jak długo klient był w systemie: InventoryItem.GetPrice (ilość, zamówienie.GetDate (), zamówienie.GetCustomer ())

Ale potem okazuje się, że oferty dwa w jednym dotyczą nie tylko zakupu wielu takich samych przedmiotów w ekwipunku, ale wielu dla dowolnego przedmiotu w kategorii InventoryCategory. W tym momencie podnosimy ręce i po prostu przekazujemy InventoryItem element zamówienia i pozwalamy mu poruszać się po wykresie odniesienia obiektu za pośrednictwem akcesoriów, aby uzyskać potrzebne informacje: InventoryItem.GetPrice (this)

TL; DR Chcę mieć niskie sprzężenie w obiektach, ale reguły biznesowe często zmuszają mnie do uzyskiwania dostępu do informacji z całego miejsca w celu podejmowania konkretnych decyzji.

Czy istnieją dobre techniki radzenia sobie z tym? Czy inni znajdują ten sam problem?

Winston Ewert
źródło
4
Na pierwszy rzut oka wydaje się, że klasa InventoryItem próbuje zrobić zbyt wiele, obliczając cenę, zwracanie danej ceny to jedno, ale ceny sprzedaży i specjalne reguły biznesowe nie powinny być adresami w InventoryItem. Wdrożyć klasę do obliczania cen i pozwolić obsłużyć zapotrzebowanie na dane z Zamówienia, Zapasu i Klienta i tak dalej.
Chris
@Chris, tak, podzielenie logiki cenowej jest prawdopodobnie dobrym pomysłem. Mój problem polega na tym, że taki obiekt będzie ściśle związany ze wszystkimi obiektami związanymi z porządkiem. Tę część mnie niepokoi i zastanawiam się, czy mogę tego uniknąć.
Winston Ewert
1
Jeśli coś, nie chcę tego nazywać, potrzebuje wszystkich tych informacji, to w jakiś sposób musi je wykorzystać, aby uzyskać pożądany rezultat. Jeśli masz już zapasy, towary, klientów itd., Logika biznesowa we własnym obszarze jest najbardziej sensowna. Naprawdę nie mam lepszego rozwiązania.
Chris

Odpowiedzi:

6

Mieliśmy w zasadzie to samo doświadczenie, w którym pracuję i rozwiązaliśmy ten problem, mając klasę OrderBusinessLogic. W przeważającej części opisany układ działa w większości naszych firm. Jest ładny, czysty i prosty. Ale w sytuacjach, w których kupiłeś dowolne 2 z tej kategorii, traktujemy to jako „wykonanie biznesowe” i każemy klasie OrderBL przeliczyć sumy, przechodząc przez potrzebne obiekty.

Czy to idealne rozwiązanie, nie. Nadal mamy jedną klasę o wiele za dużo znającą inne klasy, ale przynajmniej przenieśliśmy tę potrzebę z obiektów biznesowych do klasy logiki biznesowej.

Walter
źródło
2
W razie wątpliwości dodaj kolejną klasę.
Chris
1

Wygląda na to potrzebny jest oddzielny obiekt dyskontowa (lub ich listę), który śledzi wszystkie te rzeczy, a następnie zastosowanie dyskonta (ów) do Zakonu, coś Order.getTotal(Discount)lub Discount.applyTo(Order)lub podobny.

John Bode
źródło
Widzę, gdzie lepiej byłoby umieścić kod w Rabacie niż w obiekcie Inventory. Ale wydaje się, że obiekt rabatu nadal będzie musiał uzyskać dostęp do informacji ze wszystkich różnych obiektów, aby wykonać swoją pracę. Więc to nie rozwiązuje problemu, przynajmniej o ile widzę.
Winston Ewert
2
Myślę, że obiekt Rabat jest dobrym pomysłem, ale chciałbym, aby zaimplementował wzorzec obserwatora ( en.wikipedia.org/wiki/Observer_pattern ), przy czym Rabaty są przedmiotem (-ów) wzoru
BlackICE
1

Dostęp do danych z innych klas jest całkowicie w porządku. Jednak chcesz, aby była to relacja jednokierunkowa. Załóżmy na przykład, że ClassOrder uzyskuje dostęp do CallItem. Idealnie, ClassItem nie powinien mieć dostępu do ClassOrder. To, co myślę, że brakuje ci w klasie zamówień, to logika biznesowa, która może uzasadniać taką klasę, jak sugerował Walter.

Edycja: Odpowiedź na komentarz Winstona

Nie wydaje mi się, że w ogóle potrzebujesz przedmiotu z ekwipunku ... przynajmniej w sposób, w jaki go używasz. Zamiast tego miałbym klasę zasobów, która zarządzała bazą danych zasobów.

Odniosę się do przedmiotów magazynowych według identyfikatora. Każde zamówienie zawierałoby listę identyfikatorów zapasów i odpowiednią ilość.

Następnie obliczyłem sumę zamówienia za pomocą czegoś takiego.
Inventory.GetCost (Items, nazwa klienta, data)

Wtedy możesz mieć inną funkcję pomocnika, taką jak:

Inventory.ItemsLefts (int itemID)
Inventory.AddItem (int itemID, int ilość)
Inventory.RemoveItem (int itemID, int ilość)
Inventory.AddBusinessRule (...)
Inventory.DeleteBusinessRule (...)

Pemda
źródło
Zupełnie dobrze jest prosić o dane z blisko spokrewnionych klas. Problem pojawia się, gdy uzyskuję dostęp do danych z pośrednio powiązanych klas. Implementacja tej logiki w klasie Order wymagałaby, aby klasa Order zajmowała się bezpośrednio obiektem Inventory (który zawiera niezbędne dane cenowe). Ta część mnie niepokoi, ponieważ łączy porządek z klasami, o których (najlepiej) nie wiedziałby nic.
Winston Ewert
Zasadniczo Twoim pomysłem byłoby wyeliminowanie OrderItem, a zamiast tego Order śledzi wszystkie te informacje. Następnie łatwo jest przekazać tę informację innemu obiektowi w celu uzyskania informacji o cenach. To może zadziałać. (W konkretnym scenariuszu ten przykład jest luźno oparty na Zakonie, element był zbyt skomplikowany i naprawdę musiał być własnym obiektem)
Winston Ewert
To, co nie ma dla mnie sensu, to powód, dla którego chcesz mieć dostęp do Inwentarza przy użyciu numerów identyfikacyjnych zamiast odwołań do obiektów. Wydaje mi się, że lepiej śledzić referencje i ilości obiektów, niż identyfikatory i referencje przedmiotów.
Winston Ewert
Tak naprawdę wcale tego nie sugeruję. Myślę, że nadal powinieneś mieć klasę lub strukturę do zamawiania przedmiotów. Po prostu nie sądzę, że poszczególne przedmioty przedmiotów magazynowych powinny mieć cenę w środku, głównie dlatego, że nie jestem pewien, jak byś je dostał bez pytania o jakąś bazę danych. Zamówienie lub pozycja magazynowa byłaby ściśle klasą danych zawierającą identyfikator towaru i ilość. Sama kolejność zawierałaby listę tych obiektów.
Pemdas
Nie jestem do końca pewien, o co pytasz w drugim komentarzu. Nie wszystko musi być przedmiotem. Naprawdę nie jestem pewien, jak zlokalizowałbyś konkretny przedmiot bez jakiegoś identyfikatora.
Pemdas
0

Po zastanowieniu się nad tym wymyśliłem własną alternatywną strategię.

Zdefiniuj klasę cenową. Inventory.GetPrice()zwraca obiekt ceny

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

Teraz klasa Price (i ewentualnie niektóre klasy pokrewne) zawierają logikę wyceny, a porządek nie musi się o to martwić. Price nie wie nic o Order / OrderItem, zamiast tego po prostu wprowadza do niego informacje.

Winston Ewert
źródło