我觉得需要重新描述一下,区分依赖注入和依赖注入框架。对 Go 来说,依赖注入几乎无处不在,但没有使用任何依赖注入框架的必要。
一时半会想不到特别合适的开源项目,我就尝试描述一下做法。正文里提到了依赖注入的两个场景,一个是被动初始化,另一个是单元测试。这两个都可以用接口 Interface 来完成,方法也是一样的。
标准库里有非常多传递实例的写法,比如传一个 *http.Client 这样。如果需要多个不同组件按照特定顺序初始化,可以直接用 struct embedding 把各个实例封装起来,然后就可以传递 struct 了。
因为我的组里有很多其他背景的开发者转型而来,对于传递 struct 的做法不认可,主要的理由是他们都有要初始化几十个依赖的经历。我对此的观点是,某个组件依赖几十个其他组件这件事本身就有问题,即便是真的有很多依赖,也只是依赖其中非常小的功能而非全部。我个人认为造成这种结果的原因是用基于 class 实现的继承去套用 Go ,而 Go 的做法是完全不实现继承功能,通过组合来抽象 OO 。
第二个需求单元测试需要对代码做调整,或者说对编程思路做调整。之前我在别的 Go 语言讨论帖子里反复引用过 Accept interfaces 这个说法,如果将调用方的代码依赖从实例改成接口,就可以非常简单地实现 mocking 来做单元测试。
因为调用方无须关心调用的是哪个实例,只要这个实例实现了特定的接口即可。将调用方的方法改写为接收接口,就可以用任意的实现来 mocking 对应的功能。
如果把接口当作继承来用,那自然就会觉得依赖是个复杂的事情,因为这个接口会越来越大,最终变成了模块之间的强耦合。实际上 Go 的思维里,接口越小越好,这里的思想是组合而非继承。一个非常小的接口是很容易 mocking 的。
另外如果观察一下 github 上 Go 的 DI/mocking 相关的项目也能发现一个趋势,相比其他语言这两种框架几乎都不流行。因为真的没有必要。不过反过来说,接口就是依赖注入。 |