需求不断堆砌,代码逻辑越来越复杂,依赖越来越多,该如何组织代码结构,保持各个模块解耦? 本文来谈一下在 golang 中的依赖注入问题。

什么是 Go 中依赖注入

Dependency Injection is the idea that your components (usually structs in go) should receive their dependencies when being created. This runs counter to the associated anti-pattern of components building their own dependencies during initialization.

什么是依赖注入,来看维基百科的定义:

在软件工程中,依赖注入(dependency injection)的意思为,给予调用方它所需要的事物。
  1. “依赖”是指可被方法调用的事物。依赖注入形式下,调用方不再直接使用“依赖”,取而代之是“注入” 。
  2. “注入”是指将“依赖”传递给调用方的过程。在“注入”之后,调用方才会调用该“依赖”。
  3. 传递依赖给调用方,而不是让让调用方直接获得依赖,这个是该设计的根本需求。

简而言之,外部的依赖不应该由自身创建,而是应该从外部将依赖的对象注入。 例如:

上面的例子中,Server 依赖 Config,在 Server 的初始化中直接使用了config.New 方法;如果后面增加了参config.New数,那我们还需要到 Server 的初始化方法中修改 Conf,这实际上违背了开放封闭原则。 具体说来,上述这段的设计中存在几个明显的问题:

  • 耦合性高:Server 承担了 Config 的初始化,如果 Config 的初始化过程发生改变,我们需要一并修改 Server。
  • 扩展性差:一个 Server 的实例化过程只能构造自己的 Config,它不能使用其它已经实例化好的 Config。
  • 不利于单元测试:在完成单元测试的时候,我们无法测试不同类型的 Config 对于 Server 的影响。

为此我们可以修改成下面这样:

这样,我们可以在外部对 Config 进行初始化然后注入到 Server 中。上述修改完成后,耦合性和扩展性的问题便解决掉了,对于 Server 来说,接收从外部传入的 Config,其不关心 Config 的初始化的过程;另外,多个Server之间可共用一份 Config,并且可做到随意替换;此外,测试的时候我们可以较为轻松对关键部件进行替换。

依赖注入的好处

依赖注入处理的关键问题是解耦,解耦在代码工程学中的好处显而易见:代码扩展性强,可维护性增强以及更容易的进行单元测试。

不要隐式引用外部依赖(全局变量、隐式输入等),而是通过依赖注入的方式引入依赖。经过这样的修改之后,构造函数的依赖项就很清晰,同时也方便我们编写 mock 测试代码。

依赖注入到哪里

被依赖的模块,在创建模块时,被注入到(即当作参数传入)模块的里面。