0

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

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

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

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

编辑

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

代码

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 - 实体状态转换

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

  3. 使用“单一职责原则”强制我的容器有公共设置器

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

4

4 回答 4

3

我不会说它是一种反模式,因为反模式首先应该是一种模式(一种可识别的、广泛使用的做事方式),而且我不知道任何“存储库” -域对象”模式。

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

  • 作为域对象,它自然而合法地有责任冻结自身并改变其状态。

  • 更新和提交更改到持久存储的责任(使用 accountRepository)。

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

结果,您的 Domain 对象与太多东西紧密耦合,使其僵化和脆弱。由于太多原因,它可能会改变并可能中断。

所以没有反模式,但肯定违反了单一责任原则

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

于 2012-06-27T12:42:18.593 回答
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。

如果您选择拥有一个更加持久无知的域,您可能最好通过实现工作单元模式来做到这一点。加载/编辑/删除的实例在工作单元中注册,该单元负责在事务结束时持久化更改。工作单元负责您的变更跟踪。

如何设置取决于您创建的应用程序类型和您使用的工具。例如,我相信如果使用实体框架,您可以使用 DataContext 作为您的工作单元。(Linq-to-SQL 也有 DataContext 的概念吗?)

是使用 Entity Framework 4 的工作单元模式的示例。

于 2012-06-27T18:43:18.420 回答
1

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

1-不要向域对象注入任何东西: 不需要向域实体注入任何东西。不是服务。不是存储库。没有什么。只是纯领域模型的优点

2-让服务层直接存储库来执行 SubmitChanges,...但要注意服务层应该很薄,域对象不应该是贫血的

于 2012-06-30T12:23:52.743 回答
1

接口与单一职责原则没有直接关系。您无法将数据访问代码与业务逻辑完全分开——它们必须在某个时候进行通信!你想要做的是最小化(但不要避免)发生这种情况的地方。确保您的数据库模式是逻辑的而非物理的(即,基于谓词而不是表和列)并且基于实现的代码(例如,数据库管理系统连接驱动程序)仅在一个地方——负责与数据库。每个实体应由一个类表示。而已。

于 2012-07-01T10:06:58.040 回答