Incapsulamento nei modelli di progettazione basata sul dominio?

4

Sto usando EF Code First e ho avuto un modello come sotto.

public class Account
{
    [Required]
    public string AccountNo { get; set; }
    [Required]
    public decimal Balance { get; set; }
}

Ho utilizzato una classe di servizio per prelevare e depositare l'importo sul conto. Poi mi sono imbattuto in questo e ho realizzato che Account è anemico. Così ho felicemente aggiunto Prelevare e Depositare metodi su Account.

public class Account
{
    [Required]
    public string AccountNo { get; set; }
    [Required]
    public decimal Balance { get; set; }
    public void Withdraw(decimal amount)
    {
        // TODO: Check if enough balance is available
        Balance -= amount; 
    }
    public void Deposit(decimal amount)
    {
        Balance += amount; 
    }

}

Sono stato molto contento di questo fino a quando ho realizzato che questo non implementa correttamente l'incapsulamento. Non dovresti essere in grado di modificare direttamente il Saldo - dovresti usare metodi di prelievo e deposito. Ma nella mia soluzione puoi modificare direttamente la Balance.

Ma non posso rendere Balance una proprietà readonly poiché sto utilizzando il primo approccio al codice EF e le proprietà readonly non determineranno la creazione della colonna nella tabella del database.

Ho pensato di lasciare Account anemico e creare una classe di account di livello superiore che implementa metodi di incapsulamento e prelievo / deposito. Ma in quel caso la classe di livello superiore non avrebbe annotazioni di dati di convalida e non otterrò la funzionalità gratuita fornita dal framework.

Qual è la soluzione?

    
posta Cracker 11.08.2013 - 12:25
fonte

2 risposte

6

C'è una differenza fondamentale tra il tuo Data Access Object (DAO) e il tuo Business Object (BO). Il tuo DAO è una mappatura uno-a-uno di come si presenta il tuo negozio di dati. In questo caso, per il tuo oggetto AccountDAO , le proprietà AccountNo e Balance avranno getter e setter. Il tuo oggetto Account - gli oggetti del livello aziendale - non sarà uguale al tuo DAO.

// This is a DAO
public class AccountDAO {
   public string AccountNo { get; set; }
   public decimal Balance { get; set; }
}

// This is a business object
public class Account {
   private readonly string accountNo;
   private readonly decimal openingBalance;
   private readonly List<decimal> adjustments; // This should probably be a list of transactions.

   public class Account(string accountNo, decimal openingBalance) {
      this.accountNo = accountNo;
      this.openingBalance = openingBalance;
      this.adjustments = new List<decimal>();
   }

   public string AccountNo {
      get { return accountNo; }
   }

   public decimal Balance {
      get { return openingBalance + SumOfAdjustments(); }
   }

   public void Deposit(decimal amount) {
      adjustments.Add(amount);
   }

   public void Withdraw(decimal amount) {
      decimal testBalance = Balance - amount;
      if (testBalance < 0.0m)
         throw new AccountBalanceException("Account Balance for " + accountNo + " is less than $0.00.");

      adjustments.Add(-1.0m * amount);
   }

   private decimal SumOfAdjustments() {
      decimal sum = 0.0m;
      foreach (var adjustment in adjustments) {
         sum += adjustment;
      }
      return sum;
   }
}

// Let's come up with a good way of performing actions on an account.    
public class CashDepositActivity {
   private readonly Account account;
   private readonly decimal amount;

   public CashDepositAction(Account account, decimal amount) {
      this.account = account;
      this.amount = amount;
   }

   public void ExecuteAction() {
      // You should probably have some logging here.
      account.Deposit(amount);
   }
}

public class BalanceTransferActivity {
   private readonly Account sourceAccount;
   private readonly Account targetAccount;
   private readonly decimal amount;

   public BalanceTransferActivity(Account sourceAccount, Account targetAccount, decimal amount) {
      this.sourceAccount = sourceAccount;
      this.targetAccount = targetAccount;
      this.amount = amount;
   }

   public void ExecuteAction() {
      sourceAccount.Withdraw(amount);
      targetAccount.Deposit(amount);
   }  
}

Questo è solo un esempio, ovviamente, e non è affatto una soluzione completa. So che strumenti come EF hanno una miriade di esempi in cui l'oggetto dati, l'oggetto business e l'oggetto di visualizzazione / modifica sono tutti lo stesso oggetto, passati dal database alla vista. Nella mia esperienza, funziona alla grande su piccoli progetti in cui non c'è molta logica di business. Tuttavia, si rompe quando le regole aziendali e le restrizioni di accesso aumentano di complessità.

    
risposta data 22.08.2013 - 15:20
fonte
0

Il modo in cui lo descrivi, il saldo di un account è il risultato di tutte le transazioni applicate a tale conto. In questo caso, Balance è una proprietà di sola lettura perché è necessario modificarlo aggiungendo una transazione all'elenco associato all'account pertinente. Quindi forse una ulteriore classe di transazione ti farebbe ottenere quello che vuoi.

È anche normale avere classi più piccole e più semplici nel tuo livello di accesso ai dati che sono composte da classi nel livello aziendale. Se hai bisogno di un valore di sola lettura che possa essere caricato da un database, avere un oggetto Business Layer che può essere costruito dal tuo Data Access Layer, ma non esporlo pubblicamente, è un modo perfetto per andare. In alternativa puoi avere dei Mapper che convertono le classi Data Access Layer in classi Business Layer e viceversa.

    
risposta data 11.08.2013 - 14:13
fonte