一旦了解DI是关于模式和原理,而不是技术,这实际上很容易做到。
要以与DI容器无关的方式设计API,请遵循以下一般原则:
编程到接口,而不是实现
这个原则实际上是设计模式的引用(虽然来自内存),但是它应该始终是您的真正目标。DI只是实现这一目标的一种手段。
适用好莱坞原则
用DI的好莱坞原则说:不要叫DI容器,它会叫你。
切勿通过在代码内调用容器直接请求依赖项。通过使用构造函数注入隐式地请求它。
使用构造函数注入
当您需要依赖项时,可通过构造函数静态地请求它:
public class Service : IService
{
private readonly ISomeDependency dep;
public Service(ISomeDependency dep)
{
if (dep == null)
{
throw new ArgumentNullException("dep");
}
this.dep = dep;
}
public ISomeDependency Dependency
{
get { return this.dep; }
}
}
注意Service类如何保证其不变性。创建实例后,由于Guard子句和readonly
关键字的组合,因此可以确保依赖项可用。
如果需要短期对象,请使用Abstract Factory
使用构造函数注入注入的依赖关系通常是长期存在的,但是有时您需要一个短暂的对象,或者根据仅在运行时已知的值来构建依赖关系。
请参阅此以获取更多信息。
仅在最后负责的时刻撰写
使对象解耦,直到最后。通常,您可以等待并在应用程序的入口点连接所有内容。这称为合成根。
此处有更多详细信息:
使用立面简化
如果您觉得由此产生的API对于新手用户而言过于复杂,则可以始终提供一些封装常见依赖项组合的Facade类。
为了提供具有高度可发现性的灵活立面,您可以考虑提供Fluent Builders。像这样:
public class MyFacade
{
private IMyDependency dep;
public MyFacade()
{
this.dep = new DefaultDependency();
}
public MyFacade WithDependency(IMyDependency dependency)
{
this.dep = dependency;
return this;
}
public Foo CreateFoo()
{
return new Foo(this.dep);
}
}
这将允许用户通过编写默认Foo
var foo = new MyFacade().CreateFoo();
但是,很可能会发现可以提供自定义的依赖关系,并且您可以编写
var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();
如果您想象MyFacade类封装了许多不同的依赖项,那么我希望很清楚,它将如何提供适当的默认值,同时仍使可扩展性可发现。
FWIW,在写完这个答案很长时间之后,我扩展了本文的概念,并撰写了一篇有关DI友好库的较长博客文章,以及一篇关于DI友好框架的随笔文章。