重构代码以避免反模式

时间:2012-06-27 10:29:37

标签: c# .net design-patterns domain-driven-design cqrs

我有一个BusinessLayer项目,其中包含以下代码。域对象是FixedBankAccount(实现IBankAccount)。

  1. 存储库是域对象的公共属性,并作为接口成员。 如何重构它以使存储库不是接口成员

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

  3. 注意:存储库模式使用LINQ to SQL实现。

    修改

    下面给出的代码是更好的方法吗? https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle

    CODE

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


    public class FixedBankAccount : IBankAccount
    {
        private RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository;
        public RepositoryLayer.IRepository<RepositoryLayer.BankAccount> 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<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;
        }
    }
    


    阅读:

    1. DDD - Entity state transition

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

    3. Using the "Single Responsibility Principle" forces my containers to have public setters

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

4 个答案:

答案 0 :(得分:3)

我不会说这是一种反模式,因为反模式首先应该是一种模式(一种可识别的,广泛的做事方式)而且我不知道任何“存储库 - in-the-Domain-object“模式。

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

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

  • 更新并向持久性商店提交更改的责任(使用accountRepository)。

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

因此,您的Domain对象与太多东西紧密耦合,使其变得僵硬和脆弱。它可能会因为太多原因而改变并可能会中断。

所以没有反模式,但肯定会违反Single Responsibility Principle

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

答案 1 :(得分:3)

重构此代码以使存储库不是接口成员非常容易。存储库是实现的依赖,而不是接口 - 将其注入到具体类中,并将其从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();
    }

}

关于第二个问题......

是的,域对象通过了解您的持久性代码而违反了SRP。然而,这可能是也可能不是问题;许多框架将这些职责混合起来以产生很好的效果 - 例如,Active Record模式。它确实使单元测试更有趣,因为它需要你模拟你的IRepository。

如果您选择拥有更持久无知的域,则最好通过实施工作单元模式来实现。已加载/已编辑/已删除的实例将在工作单元中注册,该工作单元负责在事务结束时保留更改。工作单位负责您的变更跟踪。

如何设置取决于您正在创建的应用程序类型以及您正在使用的工具。我相信,如果使用Entity Framework,您可以使用DataContext作为工作单元。 (Linq-to-SQL是否也具有DataContext的概念?)

Here是使用实体框架4的工作单元模式的一个示例。

答案 2 :(得分:1)

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

1-不要向域对象注入任何内容: you do not need to inject anything into your domain entities.Not services. Not repositories. Nothing. Just pure domain model goodness

2-让服务层直接存储库执行SubmitChanges,...但要注意服务层应该&amp;域对象不应该是anemic

答案 3 :(得分:1)

接口与单一责任原则无关。您不能将数据访问代码与业务逻辑完全分开 - 他们必须在某些时候进行通信!您想要做的是最小化(但不要避免)。确保您的数据库模式是逻辑而不是物理(即,基于谓词而不是表和列)以及基于实现的代码(例如,数据库管理系统连接驱动程序) )仅在一个位置 - 负责与数据库通信的类。每个实体应由一个类表示。就是这样。