过去几年里我一直使用Java。最近,用Go建立了一个小项目,然而 Go 生态系统中依赖注入(DI)功能缺乏让我震惊。于是我决定尝试使用 Uber 的 dig 库来构建我的项目,期间感触颇深。
我发现 DI 帮助我解决了之前在 Go 应用程序中遇到的很多问题 - 过度使用 init
函数,滥用全局变量和复杂的应用程序设置等。
在这篇文章中,我将介绍 DI ,然后在使用 DI 框架(通过 dig
库)前后写一些例子做对比。
DI 的简要概述
依赖注入是指你的组件(通常在 go 中是 struct )在创建时,就应该获取它们依赖关系的一种思想。这与那些组件在初始化过程中,就建立自身依赖关系的反关联模式不同 。我们来看一个例子。
假设你构造Server
需要 Config
结构体。一种方法是在初始化期间 Server
构建 Config
。
type Server struct {
config *Config
}
func New() *Server {
return &Server{
config: buildMyConfigSomehow(),
}
}
看起来很方便。调用者甚至不必知道 Server
需要访问 Config
。这些都被我们的函数隐藏起来了。
然而,这存在一些缺点。首先,如果我们想要改变我们 Config
的构建方式,我们不得不改变所有调用构建代码的地方。例如,假设我们的 buildMyConfigSomehow
函数现在需要一个参数。每个调用处都需要访问该参数并需要将其传递给构造函数。
此外,这使得实现 Config
函数变得十分麻烦,我们得以某种方法进入 new
函数的内部,并创建Config
。
这是 DI 方式:
type Server struct {
config *Config
}
func New(config *Config) *Server {
return &Server{
config: config,
}
}
现在我们将 Server
与Config
分离 。我们可以根据自己的逻辑创造 Config
然后将结果传递给 New
函数。
此外,如果 Config
是一个接口,这为我们提供了一个简单的模拟途径 。只要 New
实现了我们的接口,就可以传递任何我们想要的东西。这使得测试实现了 Config
接口的 Server
很简单。
令人痛苦的是在创建 server
之前手动创建 config
。我们在这里创建了一个依赖关系 – 因为 server
依赖 Config,
所以需要首先创建 Config
。在真正的应用程序中,这些依赖会变得更加复杂,这会导致构建应用程序完成其工作所需的组件间的复杂逻辑 。