Unikanie rozdętych obiektów domenowych

12

Próbujemy przenieść dane z naszej rozdętej warstwy usługi do naszej warstwy domeny przy użyciu metody DDD. Obecnie w naszych usługach jest dużo logiki biznesowej, która jest rozrzucona po całym miejscu i nie korzysta z dziedziczenia.

Mamy centralną klasę domen, która jest przedmiotem większości naszej pracy - handel. Obiekt Trade będzie wiedział, jak sam wycenić, jak oszacować ryzyko, zweryfikować siebie itp. Możemy wtedy zastąpić warunki warunkowe polimorfizmem. Np .: SimpleTrade wycenia się w jedną stronę, ale ComplexTrade wyceni się w inny sposób.

Obawiamy się jednak, że spowoduje to rozdęcie klasy handlowej. To naprawdę powinno być odpowiedzialne za własne przetwarzanie, ale rozmiar klasy będzie wykładniczo zwiększał się wraz z dodawaniem kolejnych funkcji.

Mamy więc wybór:

  1. Umieść logikę przetwarzania w klasie Trade. Logika przetwarzania jest teraz polimorficzna w zależności od rodzaju transakcji, ale klasa handlu ma teraz wiele obszarów odpowiedzialności (ceny, ryzyko itp.) I jest duża
  2. Umieść logikę przetwarzania w innej klasie, takiej jak TradePricingService. Nie jest już polimorficzny z drzewem Dziedziczenia handlu, ale klasy są mniejsze i łatwiejsze do przetestowania.

Jakie byłoby sugerowane podejście?

djcredo
źródło
Nie ma problemu - chętnie przyjmę migrację!
1
Uwaga na odwrotność: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
„rozmiar klasy będzie wykładniczo zwiększał się wraz z dodawaniem większej liczby funkcji” - każdy programista powinien wiedzieć lepiej niż niewłaściwie używać słowa „wykładniczo” w ten sposób.
Michael Borgwardt
@Piskvor, który jest po prostu głupi
Arnis Lapsa
@Arnis L .: Dziękujemy za przemyślany komentarz. Zauważ, że był to cytat „migrowany z stackoverflow.com 22 listopada o 22:19”, wraz z moim komentarzem. Usunąłem teraz mój komentarz, że „lepiej byłoby w programmers.SE”; czy masz coś do dodania, czy był to jedyny pomysł, który chciałeś wyrazić?
Piskvor opuścił budynek

Odpowiedzi:

8

Jeśli wybierasz opcję Domain Driven, zastanów się nad traktowaniem klasy Trade jako zagregowanego katalogu głównego i podziel jej obowiązki na inne klasy.

Nie chcesz kończyć z podklasą handlu dla każdej kombinacji ceny i ryzyka, więc transakcja może zawierać obiekty ceny i ryzyka (skład). Obiekty Price i Risk wykonują rzeczywiste obliczenia, ale nie są widoczne dla żadnej klasy oprócz Trade. Możesz zmniejszyć wielkość handlu, nie wystawiając swoich nowych klas na świat zewnętrzny.

Spróbuj użyć kompozycji, aby uniknąć dużych drzew spadkowych. Nadmierne dziedziczenie może prowadzić do sytuacji, w których spróbujesz wytropić zachowanie, które tak naprawdę nie pasuje do modelu. Lepiej wyciągnąć te obowiązki na nową klasę.

Terry Wilcox
źródło
Zgadzam się. Myślenie o obiektach w kategoriach zachowań, które tworzą rozwiązanie (wycena, ocena ryzyka), a nie próba modelowania problemu pozwala uniknąć tych klas monolitycznych.
Garrett Hall,
Zgadzam się również. Kompozycja, wiele mniejszych klas z konkretnymi, pojedynczymi obowiązkami, ograniczone stosowanie prywatnych metod, wiele szczęśliwych interfejsów itp.
Ian
4

Twoje pytanie na pewno przypomina mi wzorzec strategii . Następnie możesz zamienić się różnymi strategiami handlowymi / cenowymi, podobnymi do tych, które nazywasz TradePricingService.

Zdecydowanie uważam, że rada, którą tu dostaniesz, to stosowanie kompozycji zamiast dziedziczenia.

Scott Whitlock
źródło
2

Jednym z możliwych rozwiązań, z których korzystałem w podobnym przypadku, jest wzorzec projektu adaptera (strona, do której się odwołuje, zawiera wiele przykładowego kodu). Prawdopodobnie w połączeniu ze wzorem projektu delegacji dla łatwego dostępu do głównych metod.

Zasadniczo dzielisz funkcjonalność Tradera na kilka odrębnych obszarów - np. Obsługa cen, ryzyko, walidacja - wszystkie mogą być różnymi obszarami. Dla każdego obszaru można następnie wdrożyć osobną hierarchię klas, która obsługuje dokładnie tę funkcjonalność w różnych potrzebnych wariantach - wszystkie te same wspólne interfejsy dla każdego obszaru. Główna klasa Trader jest następnie redukowana do najbardziej podstawowych danych i odniesień do wielu obiektów procedur obsługi, które można zbudować w razie potrzeby. Lubić

interface IPriceCalculator {
  double getPrice(ITrader t);
}
interface ITrader {
  IPriceCalculator getPriceCalculator();
}
class Tracer implements ITrader {
  private IPriceCalculator myPriceCalculator = null;
  IPriceCalculator getPriceCalculator() {
    if (myPriceCalculator == null)
      myPriceCalculator = PriceCalculatorFactory.get(this);
    return myPriceCalculator;
  }
}

Jedną z głównych zalet tego podejścia jest to, że możliwe kombinacje np. Cen i ricks są całkowicie oddzielone i dlatego można je łączyć w razie potrzeby. Jest to dość trudne w przypadku dziedziczenia jednowątkowego większości języków programowania. Decyzję o wyborze kombinacji można nawet obliczyć bardzo późno :-)

Zwykle staram się zachować klasy adaptera - np. Podklasy IPriceCalculatorpowyżej - bezstanowe. Oznacza to, że klasy te nie powinny zawierać żadnych danych lokalnych, jeśli to możliwe, aby zmniejszyć liczbę instancji, które należy utworzyć. Dlatego zwykle podam główny dostosowany obiekt jako argument we wszystkich metodach - jak getPrice(ITrader)powyżej.

Tonny Madsen
źródło
2

nie mogę wiele powiedzieć o Twojej domenie, ale

Mamy centralną klasę domen, która jest przedmiotem większości naszej pracy - handel.

... to dla mnie zapach. Prawdopodobnie spróbowałbym naszkicować różne obowiązki klasy i ostatecznie rozłożyć ją na różne agregaty. Agregaty byłyby wówczas projektowane na podstawie ról i / lub punktów widzenia zaangażowanych interesariuszy / ekspertów dziedzinowych. Jeśli cena i ryzyko są zaangażowane w ten sam przypadek zachowania / użycia, prawdopodobnie należą do tego samego agregatu. Ale jeśli są oddzielone, mogą należeć do oddzielnych agregatów.

Być może RiskEvaluation może być osobną jednostką w Twojej domenie, ostatecznie z określonym cyklem życia (nie mogę tak naprawdę powiedzieć, że spekuluję ... znasz swoją domenę, nie wiem), ale kluczem jest stworzenie ukrytych koncepcji jawne i aby uniknąć sprzężenia, które nie jest spowodowane zachowaniem, ale tylko starszym sprzężeniem danych.

Ogólnie rzecz biorąc, myślałem o oczekiwanym zachowaniu i różnych cyklach życia zaangażowanych komponentów. Samo dodanie zachowania na zgrupowanych danych tworzy nadęte obiekty. Ale dane zostały pogrupowane zgodnie z istniejącym projektem opartym na danych, więc nie trzeba się tego trzymać.

ZioBrando
źródło