Evitare oggetti di dominio gonfiati


12

Stiamo provando a spostare i dati dal nostro livello di servizio gonfiato nel nostro livello di dominio utilizzando un approccio DDD. Al momento disponiamo di molte logiche commerciali nei nostri servizi, che sono sparsi ovunque e non beneficiano dell'eredità.

Abbiamo una classe Domain centrale che è al centro della maggior parte del nostro lavoro: un trade. L'oggetto Trade saprà valutare se stesso, come stimare il rischio, validare se stesso, ecc. Possiamo quindi sostituire i condizionali con polimorfismo. Ad esempio: SimpleTrade si comporterà in un modo, ma ComplexTrade si comporterà in un altro.

Tuttavia, siamo preoccupati che questo possa gonfiare le classi commerciali. Dovrebbe davvero essere responsabile della propria elaborazione, ma la dimensione della classe aumenterà in modo esponenziale con l'aggiunta di ulteriori funzionalità.

Quindi abbiamo delle scelte:

  1. Metti la logica di elaborazione nella classe commerciale. La logica di elaborazione è ora polimorfica in base al tipo di operazione, ma la classe di attività ha ora più responsabilità (prezzi, rischio, ecc.) Ed è ampia
  2. Inserisci la logica di elaborazione in un'altra classe come TradePricingService. Non è più polimorfico con l'albero di eredità commerciale, ma le classi sono più piccole e più facili da testare.

Quale sarebbe l'approccio suggerito?


Nessun problema: sono felice di accettare la migrazione!


1
"la dimensione della classe aumenterà in modo esponenziale man mano che vengono aggiunte più funzionalità" - ogni programmatore dovrebbe conoscere meglio che usare in modo improprio la parola "esponenzialmente" in questo modo.
Michael Borgwardt,

@Piskvor è semplicemente stupido
Arnis Lapsa il

@Arnis L .: Grazie per il tuo commento ponderato. Si noti che questo è stato, citando, "migrato da stackoverflow.com il 22 novembre alle 22:19", insieme al mio commento. Ho rimosso il mio commento che "sarebbe meglio su programmers.SE"; ora, hai qualcosa da aggiungere o era l'unica idea che volevi esprimere?
Piskvor lasciò l'edificio il

Risposte:


8

Se stai andando su Domain Driven, considera di considerare la tua classe Trade come una radice aggregata e dividere le sue responsabilità in altre classi.

Non vuoi finire con una sottoclasse commerciale per ogni combinazione di prezzo e rischio, quindi un commercio può contenere oggetti Prezzo e Rischio (composizione). Gli oggetti Prezzo e Rischio eseguono i calcoli effettivi, ma non sono visibili ad alcuna classe tranne Commercio. Puoi ridurre le dimensioni del commercio, senza esporre le tue nuove classi al mondo esterno.

Cerca di usare la composizione per evitare alberi di grande eredità. Troppa eredità può portare a situazioni in cui si tenta di calzare calzascarpe in un comportamento che non si adatta al modello. È meglio estrarre queste responsabilità in una nuova classe.


Sono d'accordo. Pensare agli oggetti in termini di comportamenti che formano la soluzione (Pricing, RiskAssessment) piuttosto che cercare di modellare il problema evita queste classi monolitiche.
Garrett Hall,

Anche d'accordo. Composizione, molte classi più piccole con responsabilità singole e specifiche, uso limitato di metodi privati, molte interfacce felici, ecc.
Ian

4

La tua domanda mi fa sicuramente pensare al modello di strategia . Quindi puoi scambiare varie strategie di trading / prezzi, simili a quelle che stai chiamando a TradePricingService.

Penso sicuramente che il consiglio che otterrai qui è di usare la composizione anziché l'eredità.


2

Una possibile soluzione che ho usato in un caso simile è il modello di progettazione dell'adattatore (la pagina di riferimento contiene un sacco di codice di esempio). Forse in combinazione con il modello di progettazione della delega per un facile accesso ai metodi principali.

Fondamentalmente, dividi la funzionalità del trader in una serie di aree disgiunte - ad esempio la gestione dei prezzi, i rischi, la convalida potrebbero essere aree diverse. Per ogni area, è quindi possibile implementare una gerarchia di classi separata che gestisca quella esatta funzionalità nelle diverse varianti necessarie - tutta un'interfaccia comune per ogni area. La classe principale di Trader viene quindi ridotta ai dati e ai riferimenti più basilari a un numero di oggetti gestore, che possono essere costruiti all'occorrenza. Piace

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

Uno dei principali vantaggi di questo approccio è che le possibili combinazioni, ad esempio di prezzi e ricks, sono completamente separate e possono quindi essere combinate secondo necessità. Questo è piuttosto difficile con l'eredità a thread singolo della maggior parte dei linguaggi di programmazione. La decisione su quale combinazione usare può essere calcolata molto tardi :-)

Di solito cerco di mantenere le classi dell'adattatore - ad esempio le sottoclassi di cui IPriceCalculatorsopra - senza stato. Vale a dire che queste classi non dovrebbero contenere dati locali, se possibile, per ridurre il numero di istanze che devono essere create. Quindi di solito fornisco l'oggetto adattato principale come argomento in tutti i metodi, come in getPrice(ITrader)precedenza.


2

non posso dire molto sul tuo dominio, ma

Abbiamo una classe Domain centrale che è al centro della maggior parte del nostro lavoro: un trade.

... è un odore per me. Probabilmente proverei a delineare le diverse responsabilità della classe e alla fine scomporla in aggregati diversi. Gli aggregati verrebbero quindi progettati attorno a ruoli e / o punti di vista delle parti interessate / esperti di dominio coinvolti. Se il prezzo e il rischio sono coinvolti nello stesso comportamento / caso d'uso, probabilmente appartengono allo stesso aggregato. Ma se sono disaccoppiati potrebbero appartenere a aggregati separati.

Forse RiskEvaluation potrebbe essere un'entità separata nel tuo dominio, eventualmente con un ciclo di vita specifico (non posso davvero dire, sto solo speculando ... conosci il tuo dominio, non lo so), ma la chiave è rendere concetti impliciti esplicito e per evitare l'accoppiamento non guidato dal comportamento, ma solo dall'accoppiamento di dati legacy.

In generale, penserei al comportamento previsto e ai diversi cicli di vita dei componenti coinvolti. Basta aggiungere un comportamento in cima ai dati raggruppati per creare oggetti gonfiati. Ma i dati sono stati raggruppati secondo una progettazione basata sui dati esistente, quindi non è necessario attenersi a questo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.