为什么耦合到new关键字的依赖关系被认为是坏的?

时间:2014-12-14 08:28:34

标签: c# dependency-injection inversion-of-control decoupling

我已经使用了Dependency Injection一段时间了,现在我想向一组新开发人员讲述IoC和DI。我记得亲自向一个人解释,他问我:

“为什么不直接使用:

private IMyInterface _instance = new MyImplementaion();
而不是经历所有的DI麻烦。 “

我的回答是:“单元测试需要模拟和存根。” - 但是我们不在公司写单元测试,所以它没有说服他。我告诉他具体实现很糟糕,因为你紧密耦合到一个实现。更改一个组件将导致另一个组件发生变化。

你能举个例子吗? 你能告诉我为什么这段代码不好的原因吗?

对我而言,我似乎很难解释它: - )

1 个答案:

答案 0 :(得分:2)

以下耦合的问题

public class MyClass
{
    private IMyInterface _instance = new MyImplementation();
    ...

意味着任何时候创建MyClass(无论是直接创建还是由IoC容器创建)都会立即创建具体的MyImplementation并将其依赖项_instance绑定到此具体实现。反过来,MyImplementation可能还有其他依赖关系,它们也是以这种方式耦合的。

类的解耦的好处,使MyClass仅依赖于其依赖关系的接口,而不依赖于具体实现(即SOLID principles的D)包括:

  1. 用于单元测试 - 正如您所提到的,为了单独测试MyClass,使用new'ed依赖项,您需要求助于Moles / Fakes等令人讨厌的事情为了模拟硬连线MyImplementation依赖。

  2. 替换 - 通过仅耦合到接口,您现在可以交换IMyInterface的不同具体实现(例如,通过策略模式),而无需更改MyClass中的任何代码。

  3. 使您的系统中的依赖关系显式且明显,因为IMyInterface依赖关系可能具有进一步的依赖关系,需要解决(并且可能还需要配置注意事项)。如果MyClass在内部隐藏IMyInterface依赖关系,则调用者无法看到MyClass的依赖关系。虽然在经典的1990年代的OO中这是常见的(即封装+组合),但这可能会掩盖实现,因为仍然需要完成所有依赖项的部署。但是,在接口级别完成耦合(即MyClass的使用者仅通过IMyClass这样做),耦合可见接口为IMyClass,这将再次隐藏对IMyInterface的依赖,因为构造函数在界面上不可见。)

  4. 用于可配置的依赖生命周期控制。通过注入IMyInterface而不是新建MyImplementation,您可以在MyImplementation对象的生命周期管理方面允许其他配置选项。在MyImplementation上创建MyClass的原始硬连线时,它实际上已经拥有MyImplementation的生命周期。通过将其留给IoC容器,您现在可以使用MyImplementation生命周期的其他选项,这可能更有效,例如如果MyImplementation个实例是线程安全的,您可以选择在多个MyClass实例之间共享实例。

  5. 总之,我认为重构应该看起来适合IoC构造函数依赖注入:

    public class MyClass
    {
        // Coupled onto the the interface. Dependency can be mocked, and substituted
        private readonly IMyInterface _instance;
    
        public MyClass(IMyInterface instance)
        {
           _instance = instance;
        }
        ...
    

    IoC容器引导将定义需要绑定IMyInterface的WHICH实现,并且还将定义依赖关系的生命周期,例如:在Ninject:

     Bind<IMyInterface>()
         .To<SomeConcreteDependency>() // Which implements IMyInterface
         .InSingletonScope();