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:
- 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
- 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?
Odpowiedzi:
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ę.
źródło
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.
źródło
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ć
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
IPriceCalculator
powyż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 - jakgetPrice(ITrader)
powyżej.źródło
nie mogę wiele powiedzieć o Twojej domenie, ale
... 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ć.
źródło