重构代码以避免反模式
我有一个BusinessLayer项目,它具有以下代码。 域对象是FixedBankAccount(它实现了IBankAccount)。
-
该存储库是作为域对象的公共属性,并作为接口成员。 如何重构它,使存储库不会是一个接口成员 ?
-
域对象(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; } }
读:
-
DDD – 实体状态转换
-
https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle
-
使用“单一责任原则”迫使我的容器有公共安置者
-
https://softwareengineering.stackexchange.com/questions/150760/single-responsibility-principle-how-can-i-avoid-code-fragmentation
我不会说这是一种反模式,因为反模式首先应该是一种模式(一种可识别的,广泛的做事方式),我不知道任何“存储库“域对象”模式。
然而,这实际上是不好的做法,因为您的BankAccount域对象混合3个职责:
-
它作为域对象的自然和合法责任来冻结自己并改变其状态。
-
负责更新并提交对持久存储的更改(使用accountRepository)。
-
决定如何发送消息(在这种情况下,电子邮件)并发送它的责任。
因此,您的Domain对象与太多事物紧密结合,使其变得僵硬脆弱。 它可能会改变,可能会有很多原因。
所以没有反模式,但肯定是违反了单一责任原则 。
最后的两个职责应该移到单独的对象上。 提交更改属于pipe理业务事务(工作单元)的对象,并且知道正确的时间来结束事务并刷新事务。 第二个可以放在基础架构层的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(); } }
关于第二个问题
是的,域对象通过意识到您的持久性代码来违反SRP。 然而,这可能是也可能不是问题; 许多框架将这些责任混合在一起,从而获得巨大的效果 – 例如,Active Record模式。 它确实使unit testing更有趣一点,因为它要求你嘲笑你的IRepository。
如果你select了一个更持久的无知的领域,你可能最好通过实施工作单元模式来做到这一点。 加载/编辑/删除的实例在工作单元中注册,该单元负责在交易结束时保持更改。 工作单位负责您的更改跟踪。
这是如何设置取决于你正在创build的应用程序的types和你正在使用的工具。 我相信,如果使用entity framework,例如,您可以使用DataContext作为您的工作单元。 (Linq-to-SQL是否也有DataContext的概念?)
下面是entity framework4的工作单元模式的一个例子。
Remi的解决scheme要好得多,但更好的解决schemeIMO是这样的:
1-不要向域对象注入任何东西: 你不需要注入任何东西到你的域实体中。没有服务。 不是储存库。 没有。 只是纯粹的领域模型善良
2 – 让服务层直接存储库来执行SubmitChanges,但是要知道服务层应该很薄 ,域对象不应该是贫乏的
接口与单一责任原则没有直接关系。 您不能将数据访问代码与业务逻辑完全分开 – 他们必须在某个时刻进行通信! 你想做的是尽量减less (但不要避免 )发生这种情况。 确保你的数据库模式是逻辑的而不是物理的 (即,基于谓词而不是表和列),并且基于实现的代码(例如,数据库pipe理系统连接性驱动程序)只在一个地方 – 负责与数据库。 每个实体应该由一个类来表示。 而已。