Code de refactoring pour éviter les anti-patterns

J’ai un projet BusinessLayer qui a le code suivant. L’object de domaine est FixedBankAccount (qui implémente IBankAccount).

  1. Le référentiel est créé en tant que propriété publique de l’object de domaine et en tant que membre d’interface. Comment le refactoriser pour que le référentiel ne devienne pas un membre de l’interface ?

  2. L’object de domaine (FixedBankAccount) utilise directement le référentiel pour stocker les données. Est-ce une violation du principe de responsabilité unique? Comment le corriger?

Remarque: Le modèle de référentiel est implémenté à l’aide de LINQ to SQL.

MODIFIER

Le code donné dans la suite est-il une meilleure approche? https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle

CODE

public interface IBankAccount { RepositoryLayer.IRepository AccountRepository { get; set; } int BankAccountID { get; set; } void FreezeAccount(); } 

 public class FixedBankAccount : IBankAccount { private RepositoryLayer.IRepository accountRepository; public RepositoryLayer.IRepository AccountRepository { get { return accountRepository; } set { accountRepository = value; } } public int BankAccountID { get; set; } public void FreezeAccount() { ChangeAccountStatus(); } private void SendEmail() { } private void ChangeAccountStatus() { RepositoryLayer.BankAccount bankAccEntity = new RepositoryLayer.BankAccount(); bankAccEntity.BankAccountID = this.BankAccountID; accountRepository.UpdateChangesByAttach(bankAccEntity); bankAccEntity.Status = "Frozen"; accountRepository.SubmitChanges(); } } 

 public class BankAccountService { RepositoryLayer.IRepository accountRepository; ApplicationServiceForBank.IBankAccountFactory bankFactory; public BankAccountService(RepositoryLayer.IRepository repo, IBankAccountFactory bankFact) { accountRepository = repo; bankFactory = bankFact; } public void FreezeAllAccountsForUser(int userId) { IEnumerable accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == userId); foreach (RepositoryLayer.BankAccount repositroyAccount in accountsForUser) { DomainObjectsForBank.IBankAccount acc = null; acc = bankFactory.CreateAccount(repositroyAccount); if (acc != null) { acc.BankAccountID = repositroyAccount.BankAccountID; acc.accountRepository = this.accountRepository; acc.FreezeAccount(); } } } } 

 public interface IBankAccountFactory { DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount); } 

 public class MySimpleBankAccountFactory : IBankAccountFactory { public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount) { DomainObjectsForBank.IBankAccount acc = null; if (Ssortingng.Equals(repositroyAccount.AccountType, "Fixed")) { acc = new DomainObjectsForBank.FixedBankAccount(); } if (Ssortingng.Equals(repositroyAccount.AccountType, "Savings")) { acc = new DomainObjectsForBank.SavingsBankAccount(); } return acc; } } 

EN TRAIN DE LIRE:

  1. DDD – Transition d’état d’entité

  2. https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle

  3. L’application du “principe de responsabilité unique” oblige mes conteneurs à disposer de dispositifs de réglage publics

  4. https://softwareengineering.stackexchange.com/questions/150760/single-responsibility-principle-how-can-i-avoid-code-fragmentation

Je ne dirais pas que c’est un anti-modèle, car un anti-modèle est censé être un modèle en premier lieu (une façon de faire reconnaissable et répandue) et je ne connais pas de «référentiel en cours». Motif -domaine-object “.

Cependant, c’est certainement une mauvaise pratique IMO car votre object de domaine BankAccount mélange 3 responsabilités:

  • Sa responsabilité naturelle et légitime en tant qu’object du domaine de se figer et de changer de statut.

  • La responsabilité de mettre à jour et de soumettre les modifications à un magasin persistant (à l’aide de accountRepository).

  • La responsabilité de décider comment un message doit être envoyé (dans ce cas, un email) et de l’envoyer.

En conséquence, votre object Domain est étroitement associé à trop d’éléments, ce qui le rend rigide et fragile. Cela pourrait changer et éventuellement casser pour trop de raisons.

Donc, pas d’anti-modèle, mais une violation du principe de responsabilité unique, c’est certain.

Les 2 dernières responsabilités doivent être déplacées vers des objects séparés. La soumission des modifications appartient plutôt à un object qui gère la transaction commerciale (Unité de travail) et est informé du bon moment pour mettre fin à la transaction et tout vider. Le second pourrait être placé dans un EmailService dans la couche Infrastructure. Dans l’idéal, l’object qui effectue l’opération de gel global ne devrait pas connaître le mécanisme de remise des messages (par courrier ou autre) mais en être injecté, ce qui permettrait une plus grande flexibilité.

Refactoriser ce code pour que le référentiel ne soit pas un membre de l’interface est assez simple. Le référentiel est une dépendance de l’implémentation, pas de l’interface. Injectez-le dans votre classe concrète et supprimez-le de IBankAccount.

 public class FixedBankAccount : IBankAccount { public FixedBankAccount(RepositoryLayer.IRepository accountRepository) { this.accountRepository = accountRepository; } private readonly RepositoryLayer.IRepository accountRepository; public int BankAccountID { get; set; } public void FreezeAccount() { ChangeAccountStatus(); } private void SendEmail() { } private void ChangeAccountStatus() { RepositoryLayer.BankAccount bankAccEntity = new RepositoryLayer.BankAccount(); bankAccEntity.BankAccountID = this.BankAccountID; accountRepository.UpdateChangesByAttach(bankAccEntity); bankAccEntity.Status = "Frozen"; accountRepository.SubmitChanges(); } } 

En ce qui concerne la deuxième question …

Oui, l’object de domaine enfreint SRP en étant conscient de votre code de persistance. Cela peut ou peut ne pas être un problème, cependant; de nombreux frameworks combinent ces responsabilités pour produire un effet important – par exemple, le modèle Active Record. Cela rend les tests unitaires un peu plus intéressants, en ce sens que vous devez vous moquer de votre repository IR.

Si vous choisissez un domaine d’ignorance plus persistant, vous feriez probablement mieux de le faire en implémentant le modèle d’unité de travail. Les instances chargées / modifiées / supprimées sont enregistrées dans l’unité de travail, qui est responsable de la persistance des modifications à la fin de la transaction. L’unité de travail est responsable du suivi de vos modifications.

La manière dont cela est configuré dépend du type d’application que vous créez et des outils que vous utilisez. Je pense que si vous travaillez avec Entity Framework, par exemple, vous pourrez peut-être utiliser le DataContext comme unité de travail. (Linq-to-SQL a-t-il également la notion d’un DataContext?)

Voici un exemple de modèle d’unité de travail avec Entity Framework 4.

La solution de Remi est bien meilleure, mais une meilleure solution à l’OMI serait la suivante:

1- N’injectez rien dans les objects de domaine: vous n’avez pas besoin d’injecter quoi que ce soit dans les entités de votre domaine. Pas de services. Pas de repositorys. Rien. Juste pur bonheur de modèle de domaine

2- Laisser les référentiels directs de la couche service effectuer SubmitChanges, … mais sachez que la couche service doit être constituée d’objects fins et de domaine ne doit pas être anémique

Les interfaces n’ont rien à voir directement avec le principe de responsabilité unique. Vous ne pouvez pas séparer complètement le code d’access aux données de la logique métier: ils doivent communiquer à un moment donné! Ce que vous voulez faire est de minimiser (mais ne pas éviter ) où cela se produit. Assurez-vous que votre schéma de firebase database est logique et non physique (c’est-à-dire basé sur des prédicats et non sur des tables et des colonnes) et que le code basé sur l’implémentation (les pilotes de connectivité du système de gestion de firebase database, par exemple) ne se trouve qu’à un seul endroit: la classe responsable de la communication avec le firebase database. Chaque entité doit être représentée par une classe. C’est tout.