你应该回到老式的方式吗?
简而言之,我的回答是否定的。由于您提到的所有原因,DI 有很多好处。
我经常觉得我是为了界面而创建界面
如果您这样做,您可能违反了重用抽象原则 (RAP) http://www.codemanship.co.uk/parlezuml/blog/?postid=934
根据服务需求,某些类的构造函数可以获得
非常大,这将使该类在其他方面完全无用
未使用 IoC 的上下文。
如果您的类构造函数太大且复杂,这是向您表明您违反了另一个非常重要的原则的最佳方式:单一责任原则 https://en.wikipedia.org/wiki/Single_responsibility_principle。在这种情况下,是时候将代码提取并重构为不同的类,建议的依赖项数量约为 4 个。
为了进行 DI,您不必拥有接口,DI 只是将依赖项获取到对象中的方式。创建接口可能是能够替代依赖项以进行测试的必要方法。
除非依赖的对象是:
- 易于隔离
- 不与外部子系统(文件系统
ETC)
您可以将依赖项创建为抽象类,或者您想要替换的方法为虚拟方法的任何类。然而,接口确实创建了依赖关系的最佳解耦方式。
在某些情况下,例如在运行时实例化新实体时,一
需要访问 IoC 容器/内核才能创建实例。
这会创建对 IoC 容器本身(ObjectFactory
在 SM 中,Ninject 中的内核实例),这确实是
反对首先使用它的原因。怎么会这样
解决?我想到了抽象工厂,但这还只是更进一步
使代码变得复杂。
至于对 IOC 容器的依赖,您永远不应该在客户端类中依赖它。
他们不必这样做。
为了正确使用依赖注入,首先要理解依赖注入的概念成分根 http://blog.ploeh.dk/2011/07/28/CompositionRoot.aspx。这是唯一应该引用您的容器的地方。至此,整个对象图就构建完成了。一旦你理解了这一点,你就会意识到你的客户端永远不需要容器。因为每个客户端都只是注入其依赖项。
您还可以遵循许多其他创建模式来简化构建:
假设您要构造一个具有许多依赖项的对象,如下所示:
new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
您可以创建一个知道如何构建它的具体工厂:
public static class SomeBusinessObjectFactory
{
public static SomeBusinessObject Create()
{
return new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
}
}
然后像这样使用它:
SomeBusinessObject bo = SomeBusinessObjectFactory.Create();
您还可以使用 bad mans di 并创建一个完全不带参数的构造函数:
public SomeBusinessObject()
{
var errorHandler = new EmailErrorHandler();
var dao = new MyDao(errorHandler);
var notificationService = new SomethingChangedNotificationService(errorHandler);
Initialize(notificationService, errorHandler, dao);
}
protected void Initialize(
INotificationService notifcationService,
IErrorHandler errorHandler,
MyDao dao)
{
this._NotificationService = notifcationService;
this._ErrorHandler = errorHandler;
this._Dao = dao;
}
然后它看起来就像以前一样有效:
SomeBusinessObject bo = new SomeBusinessObject();
当您的默认实现位于外部第三方库中时,使用 Poor Man 的 DI 被认为是不好的,但当您拥有良好的默认实现时,则不太坏。
显然还有所有 DI 容器、对象构建器和其他模式。
因此,您所需要做的就是为您的对象想出一个好的创建模式。你的对象本身不应该关心如何创建依赖关系,事实上它使它们变得更加复杂并导致它们混合了两种逻辑。所以我不认为使用 DI 会降低生产力。
在某些特殊情况下,您的对象不能只注入单个实例。生命周期通常较短且需要动态实例的情况。在这种情况下,您应该将 Factory 作为依赖项注入到对象中:
public interface IDataAccessFactory
{
TDao Create<TDao>();
}
正如您所注意到的,这个版本是通用的,因为它可以利用 IoC 容器来创建各种类型(请注意,尽管 IoC 容器对我的客户端仍然不可见)。
public class ConcreteDataAccessFactory : IDataAccessFactory
{
private readonly IocContainer _Container;
public ConcreteDataAccessFactory(IocContainer container)
{
this._Container = container;
}
public TDao Create<TDao>()
{
return (TDao)Activator.CreateInstance(typeof(TDao),
this._Container.Resolve<Dependency1>(),
this._Container.Resolve<Dependency2>())
}
}
请注意,即使我有一个 Ioc 容器,我也使用了 activator,值得注意的是,工厂需要构造一个新的对象实例,而不仅仅是假设容器将提供一个新实例,因为该对象可能会注册不同的生命周期(单例) 、ThreadLocal 等)。但是,根据您使用的容器,有些容器可以为您生成这些工厂。但是,如果您确定该对象已使用 Transient 生命周期注册,则可以简单地解析它。
编辑:添加具有抽象工厂依赖项的类:
public class SomeOtherBusinessObject
{
private IDataAccessFactory _DataAccessFactory;
public SomeOtherBusinessObject(
IDataAccessFactory dataAccessFactory,
INotificationService notifcationService,
IErrorHandler errorHandler)
{
this._DataAccessFactory = dataAccessFactory;
}
public void DoSomething()
{
for (int i = 0; i < 10; i++)
{
using (var dao = this._DataAccessFactory.Create<MyDao>())
{
// work with dao
// Console.WriteLine(
// "Working with dao: " + dao.GetHashCode().ToString());
}
}
}
}
基本上,进行 DI/IoC 会极大地降低我的工作效率,并且
有些情况使代码和架构进一步复杂化
马克·西曼(Mark Seeman)就此主题写了一篇很棒的博客,并回答了这个问题:
我对这类问题的第一反应是:你说松散耦合的代码更难理解。比什么更难?
松耦合和大局 http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx
编辑:最后我想指出,并非每个对象和依赖项都需要或应该进行依赖项注入,首先考虑您使用的内容是否实际上被视为依赖项:
什么是依赖关系?
- 应用程序配置
- 系统资源(时钟)
- 第三方库
- Database
- WCF/网络服务
- 外部系统(文件/电子邮件)
上述任何对象或协作者都可能超出您的控制范围,并导致副作用和行为差异,并使测试变得困难。现在是考虑抽象(类/接口)并使用 DI 的时候了。
什么不是依赖项,并不真正需要 DI?
List<T>
- 内存流
- 字符串/原语
- 叶对象/Dto
诸如上述的对象可以在需要时使用以下方法简单地实例化new
关键词。除非有特殊原因,否则我不建议对这样简单的对象使用 DI。考虑这个问题:该对象是否在您的完全控制之下,并且不会导致任何额外的对象图或行为副作用(至少是您想要更改/控制其行为或测试的任何内容)。在这种情况下,只需将它们更新即可。
我发布了很多 Mark Seeman 帖子的链接,但我真的建议您阅读他的书和博客文章。