如何使用依赖注入完成可变对象重构?

How to accomplish mutable object refactor using dependency injection?

整个代码库中都使用了一个“Audit”对象,我试图重构它以允许依赖注入,并最终实现更好的单元测试。到目前为止,我在为我的 classes 创建接口并通过构造函数注入这些接口时没有遇到任何问题。然而,这个 class 是不同的。我看到 why/how 它有所不同,但我不确定如何修复它以使其“正常”工作。

这是一个例子(简化版本,但即使在例子中问题仍然存在):

namespace ConsoleApplication1.test.DI.Original
{
    public class MultiUseDependencies
    {
        public MultiUseDependencies()
        {

        }

        public void Update()
        {
            Audit a = new Audit();
            a.preAuditValues = "Update";

            // if data already exists, delete it
            this.Delete();

            // Update values, implementation not important

            // Audit changes to the data
            a.AuditInformation();
        }

        public void Delete()
        {
            Audit a = new Audit();
            a.preAuditValues = "Delete";

            // Delete data, implementation omitted.

            a.AuditInformation();
        }
    }

    public class Audit
    {
        public string preAuditValues { get; set; }

        public void AuditInformation()
        {
            Console.WriteLine("Audited {0}", preAuditValues);
        }
    }
}

在上面,Update 函数(未显示实现)获取数据的“更改前”版本,删除数据(并审核),inserts/updates 对数据,然后审核 insert/update.

如果我要从控制台应用程序 运行:

Console.WriteLine("\n");
test.DI.Original.MultiUseDependencies mud = new test.DI.Original.MultiUseDependencies();
mud.Update();

我会得到:

Audited Delete

Audited Update

这是预期的行为。现在 class 的实现方式,我已经可以看出会有问题,但我不确定如何更正它。使用 DI 查看(初始)重构:

namespace ConsoleApplication1.test.DI.Refactored
{
    public class MultiUseDependencies
    {

        private readonly IAudit _audit;

        public MultiUseDependencies(IAudit audit)
        {
            _audit = audit;
        }

        public void Update()
        {
            _audit.preAuditValues = "Update";

            // if data already exists, delete it
            this.Delete();

            // Update values, implementation not important

            // Audit changes to the data
            _audit.AuditInformation();
        }

        public void Delete()
        {
            _audit.preAuditValues = "Delete";

            // Delete data, implementation omitted.

            _audit.AuditInformation();
        }
    }

    public interface IAudit
    {
        string preAuditValues { get; set; }
        void AuditInformation();
    }

    public class Audit : IAudit
    {
        public string preAuditValues { get; set; }

        public void AuditInformation()
        {
            Console.WriteLine("Audited {0}", preAuditValues);
        }
    }
}

运行:

Console.WriteLine("\n");
test.DI.Refactored.MultiUseDependencies mudRefactored = new test.DI.Refactored.MultiUseDependencies(new test.DI.Refactored.Audit());
mudRefactored.Update();

我得到(如预期的那样,但不正确):

Audited Delete

Audited Delete

以上是基于实现的预期,但根据原始行为是不正确的。我不确定如何进行。原始实现依赖于不同的 Audits 来正确跟踪正在发生的变化。当我在重构中传递 IAudit 的实现时,我只得到一个 Audit 的实例,其中两者相互碰撞。

基本上在重构之前,Audit 的作用域在函数级别。重构后,Audit 的范围在 class.

有没有简单的方法来纠正这个问题?

这是一个 fiddle 的实际效果: https://dotnetfiddle.net/YbpTm4

试试这个:

public void Update()
{
    // if data already exists, delete it
    this.Delete();

    //preAuditValues should be changed after the delete or it will keep 
    //the old value

    _audit.preAuditValues = "Update";

    // Update values, implementation not important

    // Audit changes to the data
    _audit.AuditInformation();
}

或者这也应该有效:

public void Delete()
{
    string oldValue = _audit.preAuditValues;
    _audit.preAuditValues = "Delete";

    // Delete data, implementation omitted.

    _audit.AuditInformation();
    //Restoring oldValue after finished with Delete
    _audit.preAuditValues = oldValue ;
}

问题出在你的设计上。 Audit 是一个可变对象,使其成为运行时数据。注入 runtime data into the constructors of your components is an anti-pattern.

解决方案是更改设计,例如定义一个 IAudit 抽象,如下所示:

public interface IAuditHandler {
    void AuditInformation(string preAuditValues);
}

对于这个抽象,您可以创建以下实现:

public class AuditHandler : IAuditHandler {
    public void AuditInformation(string preAuditValues) {
        var audit = new Audit();
        audit.preAuditValues = preAuditValues;
        audit.AuditInformation();
    }
}

消费者现在可以依赖 IAuditHandler:

public class MultiUseDependencies
{
    private readonly IAuditHandler _auditHandler;

    public MultiUseDependencies(IAuditHandler auditHandler) {
        _auditHandler = auditHandler;
    }

    public void Update() {
        this.Delete();

        _auditHandler.AuditInformation("Update");
    }

    public void Delete() {
        // Delete data, implementation omitted.

        _auditHandler.AuditInformation("Delete");
    }
}

但我什至应该更进一步,因为使用您当前的方法,您正在用横切关注点污染业务代码。审计跟踪的代码在您的代码库中分散和复制。

然而,这将对您的应用程序设计产生很大的改变,但可能会非常有益。您绝对应该阅读 this article 以了解如何通过这种方式改进您的设计。