为什么将与new关键字的依赖关系耦合认为不好?

use*_*933 5 c# dependency-injection inversion-of-control decoupling

我使用依赖注入已经有一段时间了,现在我想向一群新开发人员介绍IoC和DI。我记得自己跟一个人解释过,他问我:

“为什么不仅仅使用:

private IMyInterface _instance = new MyImplementaion();
Run Code Online (Sandbox Code Playgroud)

而不是经历所有DI麻烦。”

我的回答是:“单元测试需要模拟和存根。” -但是我们没有在我的公司中编写单元测试,因此没有说服他。我告诉他,具体实现是不好的,因为您与一个实现紧密相关。更改一个组件将导致另一个组件的更改。

你能举一个这样的代码的例子吗?您能给我更多原因说明此代码不好的原因吗?

对我来说似乎很明显,我很难解释:-)

Stu*_*tLC 4

以下耦合的问题

public class MyClass
{
    private IMyInterface _instance = new MyImplementation();
    ...
Run Code Online (Sandbox Code Playgroud)

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

类解耦的好处MyClass是只依赖于其依赖项的接口,而不依赖于依赖项的具体实现(即SOLID 原则的 D ),包括:

  1. 对于单元测试 - 正如您所提到的,为了使用依赖项MyClass进行隔离测试new'ed,您需要诉诸令人讨厌的事情,例如Moles / Fakes模拟硬连线MyImplementation依赖项。

  2. 对于替换 - 通过仅耦合到接口,您现在可以交换不同的具体实现IMyInterface(例如通过配置 IoC 引导),而无需更改MyClass.

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

  4. 用于可配置的依赖生命周期控制。通过注入IMyInterface而不是新建MyImplementation,您可以允许与对象的生命周期管理相关的附加配置选项MyImplementation。当 的原始硬连线创建MyImplementation在 上完成时,它通过两个类实例之间的 1:1 关系MyClass有效地获得了 的生命周期的所有权。MyImplementation通过将其留给 IoC 容器,您现在可以使用 的MyImplementation生命周期的其他选项,这可能会更有效,例如,如果MyImplementation实例是线程安全的,您可以选择在 的多个实例之间共享一个实例MyClass

总之,我认为重构应该适合 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;
    }
    ...
Run Code Online (Sandbox Code Playgroud)

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

 Bind<IMyInterface>()
     .To<SomeConcreteDependency>() // Which implements IMyInterface
     .InSingletonScope();
Run Code Online (Sandbox Code Playgroud)