Modello non anemico e SRP

2

Sto implementando una funzionalità nel sistema legacy in questo momento. Invece di mettere tutta la mia logica in alcuni servizi fittizi di applicazione e con modelli anemici, ho pensato di provare a fare qualche OOP questa volta.

Non sto usando DDD, ma sto provando a seguire un po 'lo stile di DDD.

Quindi immagina una situazione in cui hai:

public class CustomerAccount {
    private CustomerAccountId customerAccountId;
    Collection<AccountEntry> accountHistory; (or maybe some AccountHistory class)
}

ora, il nostro account ha una logica di dominio. Dovrebbe essere in grado di calcolare il debito totale, il credito e il saldo medio entro un certo periodo di tempo.

Quindi sarebbe come seguire

public Money calculateTotalDebit(Date start, Date end) // or maybe some TimeInterval, it doesn't matter

public Money calculateTotalCredit(Date start, Date end)

public Money calculateAverageBalance(Date start, Date end, FancyCalculationStrategy strategy)

Ha anche alcuni vincoli che dovrebbero essere preservati durante il richiamo di altre operazioni come addAccountEntry (voce AccountEntry)

Quindi posso presumere che il mio account sia qualcosa di simile alla radice aggregata di DDD. Ovviamente, non è una vera radice aggregata, perché non sto applicando DDD, ma diciamo che vedo alcune somiglianze.

E ora cresce la mia classe Account - c'è molta logica in ognuno di questi metodi. In che modo questo è collegato al principio di responsabilità unica? Una classe dovrebbe avere solo un motivo per cambiare, ma in questo caso ce ne sono altri. O forse no? In qualche modo, sento che tutta questa logica dovrebbe appartenere a un posto.

Sì, posso creare la classe AccountHistory, che incapsula il comportamento, ma questo non risolve il problema - in tal caso, la classe AccountHistory inizierà a crescere.

Quello che posso fare, è solo per crearne un po ', diciamo

AverageBalanceCalculator

e delegare il comportamento.

Ma non sta andando di nuovo verso il modello anemico? Probabilmente questo calcolatore non ha stato e si comporterà proprio come un servizio.

Ovviamente non deve essere visibile all'utente finale - dal punto di vista dell'implementazione, potrebbe essere una classe privata del pacchetto.

In che modo SRP è in relazione con gli aggregati root e altri schemi tattici di DDD in generale?

Sono poco confuso, apprezzerei alcune risposte.

    
posta slnowak 15.12.2014 - 17:10
fonte

2 risposte

2

Sebbene esista una certa logica nell'avere i metodi che eseguono alcune operazioni con la cronologia delle fatture nella classe CustomerAccount (o Bill), si potrebbe obiettare che averli lì interrompe l'SRP. Potresti dire che la classe CustomerAccount dovrebbe essere responsabile solo della gestione dei dati dell'account (aggiunta, eliminazione o modifica di tali dati). Le segnalazioni potrebbero essere viste come una diversa tazza di tè lì. Quindi, quello che potresti fare è ricorrere, per esempio, a un modello di comando. Potresti fare qualcosa di simile:

interface IAccountReport
{
    public Money CalculateReport(Collection<AccountEntry> accountHistory);
}

class CalculateAverageBalance : IAccountReport
{
    private DateTime start;
    private DateTime end;

    public CalculateAverageBalance(DateTime start, DateTime end) : base()
    {
        this.start = start;
        this.end = end;
    }

    public Money CalculateReport(Collection<AccountEntry> accountHistory)
    {
        //Implement logic for calculating average balance using the start 
        //and end date, as well as accountHistory
        return new Money();
    }
}

class CustomerAccount
{
    private Collection<AccountEntry> accountHistory;

    public void ProvideReport (IAccountReport report)
    {
        report.CalculateReport(accountHistory);
    }
}

Quindi, per ogni nuovo tipo di rapporto, implementeresti una nuova classe, et voilà, hai delle responsabilità separate. Naturalmente, se la privacy della cronologia dell'account è obbligatoria, è possibile introdurre una gestione delle autorizzazioni in termini di quali rapporti possono essere eseguiti su quali account e da chi, ma che possono essere tutti inseriti in questo tipo di design.

    
risposta data 04.07.2016 - 13:45
fonte
1

Stai pensando troppo a questo. Se la tua classe è:

public class Bill {
    private BillId billId;
    Collection<BillEntry> billHistory; 
}

Quindi il tuo metodo di bilancia media appartiene correttamente alla classe Bill.

public class Bill {
    private BillId billId;
    Collection<BillEntry> billHistory;

    public decimal GetAverageBalance()
    {
         return billHistory.Sum(x => x.Balance);
    }
}

Tuttavia, dovresti riflettere attentamente su come lo stai modellando. Normalmente, i saldi medi sono calcolati su un oggetto Conto cliente, non su una bolletta.

    
risposta data 15.12.2014 - 17:50
fonte