如何使用依赖注入完成可变对象重构?
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
以上是基于实现的预期,但根据原始行为是不正确的。我不确定如何进行。原始实现依赖于不同的 Audit
s 来正确跟踪正在发生的变化。当我在重构中传递 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 以了解如何通过这种方式改进您的设计。
整个代码库中都使用了一个“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
以上是基于实现的预期,但根据原始行为是不正确的。我不确定如何进行。原始实现依赖于不同的 Audit
s 来正确跟踪正在发生的变化。当我在重构中传递 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 以了解如何通过这种方式改进您的设计。