重构代码以避免反模式[英] Refactoring code to avoid anti-pattern

本文是小编为大家收集整理的关于重构代码以避免反模式的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我有一个具有以下代码的BusinessLayer项目.域对象是firdbankaccount(实现IbankAccount).

  1. 存储库作为域对象的公共属性制作,并作为接口成员制作. 如何重构它,以便存储库将不会成为接口成员?

  2. 域对象(filexbankaccount)使用存储库来存储数据.这违反了单一责任原则吗?如何纠正它?

注意:存储库模式是使用Linq到SQL实现的.

编辑

是否在以下更好的方法中给出了代码? https://codereview.stackexchange.com/questions/13148/is-good-code-code-code-code-code-te-satisfy-single-single-single-responsibility-princibility-princibility-principer-principer-principer-principer-pro- https://codereview.stackexchange.com/questions/questions/questions/13148/is-it-it-it-od-code to-single-single-责任性基本

代码

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- ddd-实体状态过渡

  2. https://codereview. stackexchange.com/questions/13148/is-it-good-code-code-code-sitle-single-responsibility-principer-principer

  3. 使用"单一责任原则"迫使我的容器拥有公共设置

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

推荐答案

我不会说这是一个反模式"域中的存储库"模式.

但是,这肯定是不良练习IMO,因为您的bankAccount域对象混合了3个职责:

  • 它作为冻结自己并改变其状态的领域对象的自然和合法责任.

  • 有责任更新和提交更改为持久商店(使用AccountrePository).

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

结果,您的域对象与太多的东西紧密耦合,使其僵硬而脆弱.它可能会改变并可能因太多原因而破裂.

所以没有反pattern,但违反了单个责任原则当然. p>

最后2个职责应移至单独的对象.提交更改而不是属于管理业务交易(工作单位)的对象,并意识到结束交易和冲洗事项的正确时间.第二个可以放在基础架构层中的电子邮件服务中.理想情况下,进行全局冻结操作的对象不应意识到消息传递机制(通过邮件或其他内容),而应注入它,这将允许更具灵活性.

其他推荐答案

重构此代码,使存储库不可接受接口成员.存储库是实现的依赖性,而不是接口 - 将其注入您的具体类,并将其从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.但是,这可能是一个问题,也可能不是问题.许多框架将这些责任融合在一起以产生极大的效果 - 例如,主动记录模式.它确实使单元测试变得更加有趣,因为它需要您模拟您的iRepository.

如果您选择具有更持久的尊贵域,则最好通过实现工作模式来做到这一点.已加载/编辑/删除的实例在工作单位中注册,该单位负责在交易结束时持续更改.工作单位负责您的变更跟踪.

设置的设置如何取决于您创建的应用程序类型和所使用的工具.我相信,例如,如果与实体框架合作,则可以将DataContext用作工作单位. (Linq到SQL是否也有DataContext的概念?)

在这里是实体框架4的工作单位的示例.

其他推荐答案

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

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

2-让服务层指导存储库进行subbitchanges,但是请注意,服务层应为 thin &域对象,不应为贫血

本文地址:https://www.itbaoku.cn/post/627520.html

问题描述

I have a BusinessLayer project which has the following code. The domain object is FixedBankAccount (which implements IBankAccount).

  1. The repository is made as a public property of the domain object and is made as an interface member. How to refactor it so that repository will not be an interface member?

  2. The domain object (FixedBankAccount) makes use of the repository directly to store the data. Is this a violation of Single Responsibility Principle? How to correct it?

Note: The repository pattern is implemented using LINQ to SQL.

EDIT

Is the code given in the following a better approach? 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

推荐答案

I wouldn't say that it's an anti-pattern since an anti-pattern is supposed to be a pattern in the first place (a recognizable, widespread way of doing things) and I don't know of any "Repository-in-the-Domain-object" pattern.

However, it's certainly bad practice IMO because your BankAccount domain object mixes 3 responsibilities :

  • Its natural and legitimate responsibility as a Domain object to freeze itself and change its status.

  • The responsibility to update and submit changes to a persistent store (using the accountRepository).

  • The responsibility to decide how a message should be sent (in that case, an email) and send it.

As a result, your Domain object is tightly coupled to too many things, making it rigid and fragile. It could change and possibly break for too many reasons.

So no anti-pattern but a violation of the Single Responsibility Principle for sure.

The last 2 responsibilities should be moved to separate objects. Submitting changes rather belongs in an object that manages the business transaction (Unit of Work) and is aware of the right time to end the transaction and flush things. The second one could be placed in an EmailService in the Infrastructure layer. Ideally, the object that does the global Freeze operation shouldn't be aware of the message delivery mechanism (by mail or something else) but should be injected with it instead, which would allow for more flexibility.

其他推荐答案

Refactoring this code so that the repository is not an interface member is easy enough. The repository is a dependency of the implementation, not the interface - inject it into your concrete class, and remove it from the 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();
    }

}

In regards to the second question...

Yes, the domain object is violating SRP by being aware of your persistence code. This may or may not be a problem, however; many frameworks mix these responsibilities for great effect - for example, the Active Record pattern. It does make unit testing a little more interesting, in that it requires you to mock your IRepository.

If you choose to have a more persistent-ignorant domain, you would probably best do so by implementing the Unit of Work pattern. Loaded/edited/deleted instances get registered in the Unit of Work, which is responsible for persisting changes at the end of the transaction. The unit of work is responsible for your change tracking.

How this is setup depends on the type of application you're creating and the tools you're using. I believe if working with Entity Framework, for example, you may be able to use the DataContext as your unit of work. (Does Linq-to-SQL have the notion of a DataContext as well?)

Here's an example of the Unit of Work pattern with Entity Framework 4.

其他推荐答案

The solution of Remi is much better, but a better solution IMO would be this:

1- Don't inject anything to domain objects: you do not need to inject anything into your domain entities.Not services. Not repositories. Nothing. Just pure domain model goodness

2- Let Service layer direct repositories to do SubmitChanges,... but be aware that service layer should be thin & domain objects should not be anemic