C# 重构代码以避免反模式

C# 重构代码以避免反模式,c#,.net,design-patterns,domain-driven-design,cqrs,C#,.net,Design Patterns,Domain Driven Design,Cqrs,我有一个BusinessLayer项目,它有以下代码。域对象是FixedBankAccount(它实现了IBankAccount) 存储库作为域对象的公共属性创建,并作为接口成员创建如何重构它,使存储库不再是接口成员 域对象(FixedBankAccount)直接使用存储库来存储数据。这是否违反了单一责任原则?如何纠正 注意:存储库模式是使用LINQtoSQL实现的 编辑 下面给出的代码是更好的方法吗? 代码 public interface IBankAccount { Reposi

我有一个BusinessLayer项目,它有以下代码。域对象是FixedBankAccount(它实现了IBankAccount)

  • 存储库作为域对象的公共属性创建,并作为接口成员创建如何重构它,使存储库不再是接口成员

  • 域对象(FixedBankAccount)直接使用存储库来存储数据。这是否违反了单一责任原则?如何纠正

  • 注意:存储库模式是使用LINQtoSQL实现的

    编辑

    下面给出的代码是更好的方法吗?

    代码

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


    阅读:


  • 我不会说这是一种反模式,因为反模式首先应该是一种模式(一种可识别的、普遍的做事方式),我不知道有任何“域对象中的存储库”模式

    但是,在IMO中,这肯定是一种不好的做法,因为您的BankAccount域对象混合了3种职责:

    • 作为域对象,其自然和合法的责任是冻结自身并改变其状态

    • 负责更新并将更改提交到持久性存储(使用accountRepository)

    • 决定如何发送消息(在这种情况下,是电子邮件)并发送消息的责任

    因此,您的域对象与太多的对象紧密耦合,使其变得僵硬而脆弱。由于太多的原因,它可能会改变,也可能会破裂

    所以没有反模式,但肯定违反了规则


    最后2项职责应转移到单独的对象。提交更改属于管理业务事务(工作单元)的对象,并且知道结束事务和刷新事务的正确时间。第二个可以放在基础架构层的EmailService中。理想情况下,执行全局冻结操作的对象不应该知道消息传递机制(通过邮件或其他方式),而应该将其注入,这将允许更大的灵活性。

    重构此代码以使存储库不是接口成员是很容易的。存储库是实现的依赖项,而不是接口-将其注入到具体类中,然后从IBankAccount中删除它

    public class FixedBankAccount : IBankAccount
    {
        public FixedBankAccount(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository)
        {
            this.accountRepository = accountRepository;
        }
    
        private readonly RepositoryLayer.IRepository<RepositoryLayer.BankAccount> 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();
        }
    
    }
    
    公共类固定银行账户:IBankAccount
    {
    公共固定银行帐户(RepositoryLayer.i存储帐户存储库)
    {
    this.accountRepository=accountRepository;
    }
    私有只读RepositoryLayer.i存储帐户存储库;
    public int BankAccountID{get;set;}
    公共帐户()
    {
    ChangeAccountStatus();
    }
    私有void sendmail()
    {
    }
    私有void ChangeAccountStatus()
    {
    RepositoryLayer.BankAccount bankAccEntity=新的RepositoryLayer.BankAccount();
    bankAccEntity.BankAccountID=此.BankAccountID;
    accountRepository.UpdateChangesByTach(bankAccEntity);
    bankAccEntity.Status=“冻结”;
    accountRepository.SubmitChanges();
    }
    }
    
    关于第二个问题

    是的,域对象由于知道您的持久性代码而违反了SRP。然而,这可能是问题,也可能不是问题;许多框架将这些职责混合在一起以获得巨大的效果,例如,活动记录模式。它确实让单元测试变得更有趣,因为它要求您模拟IRepository

    如果您选择拥有一个更持久的未知域,那么最好实现工作单元模式。加载/编辑/删除的实例在工作单元中注册,工作单元负责在事务结束时保存更改。工作单位负责您的变更跟踪

    设置方式取决于您创建的应用程序类型和使用的工具。例如,我相信如果使用实体框架,您可能能够使用DataContext作为您的工作单元。(LINQtoSQL是否也有DataContext的概念?)


    这是实体框架4的工作单元模式的一个例子。

    Remi的解决方案要好得多,但IMO的更好解决方案是:

    1-不要向域对象注入任何内容:


    2-让服务层指导存储库进行提交更改,。。。但是请注意,服务层应该是薄的,域对象不应该是薄的,接口与单一责任原则没有直接关系。您不能将数据访问代码与业务逻辑完全分离,它们必须在某个时刻进行通信!您要做的是将发生这种情况的地方最小化(但不要避免)。确保您的数据库模式是逻辑的而不是物理的(即基于谓词而不是表和列),并且基于实现的代码(例如,数据库管理系统连接驱动程序)仅位于类负责与数据库对话的一个位置。每个实体应由一个类表示。就是这样。

    也许代码检查是个更好的地方?FWIW在大多数依赖注入场景中,存储库必须在类上公开,DI才能工作(即,在面值违反封装的情况下),但是,不需要将依赖项(如存储库)添加到接口中。对类实例的所有访问都将通过接口进行,因此存储库类属性是公共的这一事实是非事件的。然而,由于您使用工厂方法进行实例化,所以不需要公共依赖项。谢谢。你能提供一些演示代码给你解释吗?谢谢。我正在使用LINQ2SQL。它有数据内容
    public class BankAccountService
    {
        RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository;
        ApplicationServiceForBank.IBankAccountFactory bankFactory;
    
        public BankAccountService(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> repo, IBankAccountFactory bankFact)
        {
            accountRepository = repo;
            bankFactory = bankFact;
        }
    
        public void FreezeAllAccountsForUser(int userId)
        {
            IEnumerable<RepositoryLayer.BankAccount> 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 (String.Equals(repositroyAccount.AccountType, "Fixed"))
            {
                acc = new DomainObjectsForBank.FixedBankAccount();
            }
    
            if (String.Equals(repositroyAccount.AccountType, "Savings"))
            {
                acc = new DomainObjectsForBank.SavingsBankAccount();
            }
    
            return acc;
        }
    }
    
    public class FixedBankAccount : IBankAccount
    {
        public FixedBankAccount(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository)
        {
            this.accountRepository = accountRepository;
        }
    
        private readonly RepositoryLayer.IRepository<RepositoryLayer.BankAccount> 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();
        }
    
    }