我有一个BusinessLayer项目,其中包含以下代码。域对象是FixedBankAccount(实现IBankAccount)。
存储库是域对象的公共属性,并作为接口成员。 如何重构它以使存储库不是接口成员?
域对象(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;
}
}
阅读:
答案 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)
接口与单一责任原则无关。您不能将数据访问代码与业务逻辑完全分开 - 他们必须在某些时候进行通信!您想要做的是最小化(但不要避免)。确保您的数据库模式是逻辑而不是物理(即,基于谓词而不是表和列)以及基于实现的代码(例如,数据库管理系统连接驱动程序) )仅在一个位置 - 负责与数据库通信的类。每个实体应由一个类表示。就是这样。