2012-06-27 213 views
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

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; 
    } 
} 


READING:

  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

+1

也许代码审查它是一个更好的地方? – gdoron

+0

FWIW在大多数依赖注入场景中,必须在* class *上公开关于DI的工作方式(即在面值违反封装的情况下),然而,依赖项(如存储库)不需要添加到接口。对类实例的所有访问都将通过接口进行,因此Repository类属性为public的事实是非事件。但是,由于您正在使用工厂方法进行实例化,因此不需要公共依赖关系。 – StuartLC

回答

3

我不会说这是一个反模式,因为反模式应该是摆在首位的模式(可识别的,广泛的做事方式),我不知道任何“存储库在域对象”模式。

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

  • 它的自然和合法的责任Domain对象冻结本身并改变其状态。

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

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

因此,您的Domain对象紧密耦合到太多东西,使它变得僵硬和脆弱。由于太多原因,它可能会发生变化并可能中断。

因此,没有反模式,但Single Responsibility Principle的肯定违规。

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

+0

谢谢。你能否提供一些演示代码供你解释? – Lijo

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到SQL有一个DataContext的概念呢?)

Here的工作模式与实体框架4.

+0

谢谢。我正在使用LINQ 2 SQL。它有DataContext。您能否介绍一下如何更改代码(在LINQ 2 SQL中)使其成为工作单元? – Lijo

+0

+1,因为这基本上也是我的看法。 –

+0

@Lijo对不起,我没有使用Linq 2 SQL,并且不太了解它能够评论。祝你好运。 –

1

该股雷米的解决方案是更好的例子,但一个更好的解决办法国际海事组织将是这样:

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

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

+0

我的偏好也是使用更“纯”的域 - 当然是持久性 - 无知的域。 –

1

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