C# 重构代码以避免反模式
我有一个BusinessLayer项目,它有以下代码。域对象是FixedBankAccount(它实现了IBankAccount)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
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();
}
}