有几种情况下,队友不同意如何使用DI。
我需要为每个循环创建一个新实例。我无法将工作实例传递给ctor。
for(var item in items){
var job = new Job();
job.execute(item)
}
一种选择是将IContainer
注入课程并使用container.Resolve<Job>()
。但这是反模式和容器暴露。另一种选择是注入工厂并将容器注入工厂。工厂方法最终将使用container.Resolve<T>()
这是略有不同但容器仍然暴露并且是反模式。
注入类被破坏的常见位置,例如,WCF客户端,如果处于默认状态,我们需要新客户端。据我所知,大多数容器都处理它,我怎么能在需要时创建一个新的注入类实例。这是更好控制所必需的。例如,如果WCF连接一旦创建并关闭或默认,则类不应该能够重新创建客户端。例如,Windsor将在关闭或默认时创建新的客户端实例。
一旦我班上的方法有一次性类的依赖。注入一次性课堂,对我来说似乎不好。由于它仅在一种方法中使用,因此无论其他方法被调用,仍然需要注入。如果长期使用依赖类,则无法处理。我可以在方法本身中使用using
构造。
我的服务类依赖于太多的存储库,每个存储库只使用一个方法。注入所有存储库对我来说没有意义,尽管对性能的影响很小。我建议注入存储库工厂,但随后它将成为反模式,容器需要注入工厂。
我的代码审阅者有偏见(或我),他不喜欢看到用于创建实例的新关键字。对他而言,因为我使用的是新的,所以它不是可测试的代码,我不是在跟踪DI。所以我无法创建新的DTO,例如DefaultPaymentOption
必须注入Order
实例。如果我将DefaultPaymentOption
类注入Order
,我会忽略DTO的简单性,并且它也违反了数据库数据。草稿模式下的订单不需要DefaultPaymentOption
。我们是否过度DI?根据我的理解,DI应该用于对象从外部世界做一些动作,而不是用于合成。
我面临的另一个问题&#34; Not New&#34;方法,我必须将内部类库暴露给public(仅适用于该库),以便在主机应用程序的bootstrapper中注册。例如,CustomJob库中的CustomJobExecuter(类型为IJobExecutor)需要在主机应用程序中注册,并且为了注入它,我需要公开它。虽然我只能注册CustomJobManager并创建CustomJobExecutor的新实例并使用它。
答案 0 :(得分:1)
我对你的DI方法所看到的一个指导性问题来自对用于的误解。当您尝试将系统的实现细节抽象为通过单元测试单独测试的小类时,您希望使用DI。如果你创建一个具有内部类的具体类,那么很可能你的类工作太多了,应该分解为单元测试的组成部分。这样,当某些东西中断时,你几乎可以立即知道问题是基于代码的哪个部分破坏了。
我会更加深入地了解SOLID设计原理和单元测试,以更加扎实地理解这些模式背后的目的。
在某些时候你需要做一些实际的工作,并且为了做到这一点,新建一个实例是完全没问题的。例如,在与db2 / AS400交互时,我们没有为我们的数据服务层编写测试的选项,并且数据服务层新闻连接实例以便写入数据。
这取决于您的DI容器,并且是一个单独的问题。我知道在SimpleInjector中你可以做一个RegisterPerRequest调用,每次都需要返回新的实例。
一次性类是一个实现细节,您可以自由地新建一个实例,以便将实现细节限制在该特定类中。或者,您可以在完成所需职责后处理处理逻辑的层中将其包装。
听起来你的服务正在成为一个神的对象,并且有太多的责任。这项服务的责任是什么?
查看DTO的automapper,因为它允许您通过模拟对var dto = mappingEngine.Map(model)的调用来模拟IMappingEngine并启用单元测试;
是的,如果要对内部课程进行测试,则需要将其公开。
答案 1 :(得分:0)
从我的角度来看,new
关键字没有任何问题。它是语言的一部分,所以它可以被使用,但作为其他一切你必须明智地使用它。 DI的发明是为了使代码更易于测试,模块化且易于维护
如果你的例子中的job
没有需要容器的依赖项(存储库,服务等),是可测试的,并且没有向你的依赖项引入新的循环,那么,imho,new
是完全的好。如果将来需要容器中的依赖项,那么您将new
替换为container.get(...)
。与DTO相同 - 从容器中获取它们是过度的,因为它们应该没有逻辑和依赖
并且您不必公开课程。 1)某些语言提供比公共和私有更多的可见范围(例如java - 默认范围)。 2)你不必单独测试每个类 - 它被称为单元测试,而不是类测试。但可悲的事实是:有时你必须放松能见度以便于测试
答案 2 :(得分:0)
如果在运行时需要实例,则应始终使用Abstract Factory来提供它,而不是DI容器。您显然假设DI容器应该在任何地方创建每个实例,但这会使您直接进入反模式区域,因为现有的应用程序几乎不需要创建短期实例。
public interface ISomeObject
{
void DoSomething();
}
public interface ISomeConfigurationDependency
{
// A dependency that is configured in the DI container.
}
public interface ISomeRuntimeDependency
{
// A dependency created by some other factory at runtime.
}
public interface ISomeObjectFactory
{
ISomeObject Create(string someRuntimeValue, ISomeRuntimeDependency someRuntimeDependency);
void Release(ISomeObject someObject);
}
public class SomeObject : ISomeObject
{
private readonly string someRuntimeValue;
private readonly string someConfigurationValue;
private readonly ISomeRuntimeDependency someRuntimeDependency;
private readonly ISomeConfigurationDependency someConfigurationDependency;
public SomeObject(
string someRuntimeValue,
string someConfigurationValue,
ISomeRuntimeDependency someRuntimeDependency,
ISomeConfigurationDependency someConfigurationDependency)
{
if (string.IsNullOrEmpty(someRuntimeValue))
throw new ArgumentNullException("someRuntimeValue");
if (string.IsNullOrEmpty(someConfigurationValue))
throw new ArgumentNullException("someConfigurationValue");
if (someRuntimeDependency == null)
throw new ArgumentNullException("someRuntimeDependency");
if (someConfigurationDependency == null)
throw new ArgumentNullException("someConfigurationDependency");
this.someRuntimeValue = someRuntimeValue;
this.someConfigurationValue = someConfigurationValue;
this.someRuntimeDependency = someRuntimeDependency;
this.someConfigurationDependency = someConfigurationDependency;
}
public void DoSomething()
{
Console.WriteLine("I am using the dependencies now");
}
}
public class SomeObjectFactory : ISomeObjectFactory
{
private readonly string someConfigurationValue;
private readonly ISomeConfigurationDependency someConfigurationDependency;
public SomeObjectFactory(string someConfigurationValue, ISomeConfigurationDependency someConfigurationDependency)
{
if (string.IsNullOrEmpty(someConfigurationValue))
throw new ArgumentNullException("someConfigurationValue");
if (someConfigurationDependency == null)
throw new ArgumentNullException("someConfigurationDependency");
this.someConfigurationValue = someConfigurationValue;
this.someConfigurationDependency = someConfigurationDependency;
}
public ISomeObject Create(string someRuntimeValue, ISomeRuntimeDependency someRuntimeDependency)
{
if (string.IsNullOrEmpty(someRuntimeValue))
throw new ArgumentNullException("someRuntimeValue");
if (someRuntimeDependency == null)
throw new ArgumentNullException("someRuntimeDependency");
// The only thing a factory does is new up an instance -
// no business logic here. There is nothing to test
// here because nothing can go wrong.
return new SomeObject(
someRuntimeValue,
this.someConfigurationValue,
someRuntimeDependency,
this.someConfigurationDependency);
}
// Use release if you want to expose the factory as a formal extension point
// that might have unmanaged dependencies.
public void Release(ISomeObject someObject)
{
var disposable = someObject as IDisposable;
if (disposable != null)
disposable.Dispose();
}
}
public class SomeService : ISomeService
{
private readonly ISomeObjectFactory someObjectFactory;
private readonly ISomeRuntimeDependencyFactory someRuntimeDependencyFactory;
public SomeService(ISomeObjectFactory someObjectFactory, ISomeRuntimeDependencyFactory someRuntimeDependencyFactory)
{
if (someObjectFactory == null)
throw new ArgumentNullException("someObjectFactory");
if (someRuntimeDependencyFactory == null)
throw new ArgumentNullException("someRuntimeDependencyFactory");
this.someObjectFactory = someObjectFactory;
this.someRuntimeDependencyFactory = someRuntimeDependencyFactory;
}
public void Run()
{
// Setup runtime variables
var someRuntimeValue = "test";
var someRuntimeDependency = this.someRuntimeDependencyFactory.Create();
// Get a runtime instance
var someObject = this.someObjectFactory.Create(someRuntimeValue, someRuntimeDependency);
try
{
someObject.DoSomething();
}
finally
{
this.someObjectFactory.Release(someObject);
}
}
}
因此,你需要连接你的SomeService,SomeObjectFactory,someConfigurationValue和SomeRuntimeDependencyFactory来注入DI。 SomeObjectFactory在运行时很好地创建和清理了SomeObject实例(如果SomeObject具体类型实现了IDisposable)。
SomeRuntimeDependencyFactory与SomeObjectFactory完全相同 - 它只运行new关键字,并可选择为新实例提供依赖关系。确切的依赖性取决于具体类型。请注意,依赖项不是接口的一部分。
在这种情况下,您可以很好地测试SomeObject和SomeService类。没有理由测试抽象工厂,因为new关键字绝对不会出错。没有。所以新关键字不是你的敌人。除了创建实例之外,确保您的工厂不做任何其他操作,否则您将创建无法测试的业务逻辑。
至于对单个服务有太多依赖关系,这是一种代码气味,表明您可能违反了单一责任原则。您需要将服务重构为aggregate services。
关于建立图书馆DI Friendly,请参阅this article。如果您实际上有一个框架并且正在尝试将主机应用程序插入其中,请按照其中的链接进行操作。