依赖注入是一个经典的设计模式,在解决复杂的对象依赖关系方面是一个非常行之有效的手段。
对于有反射功能的语言来说,实现依赖注入都比较方便一些。在 Golang 中有几个比较知名的依赖注入开源库,例如 google/wire、uber-go/dig 以及 facebookgo/inject 等。
facebookgo/inject
依赖注入的背景
对于稍微复杂些的项目,我们往往就会遇到对象之间复杂的依赖关系。手动管理和初始化这些管理关系将会极其繁琐,依赖注入可以帮我们自动实现依赖的管理和对象属性的赋值,将我们从这些繁琐的依赖管理中解放出来。
以一个常见的 HTTP 服务为例,我们在开发后台时往往会把代码分为 Controller、Service 等层次。如下:
上述的代码例子中,有两个 Controller:UserController 和 PostController,分别用来接收用户和文章的相关请求逻辑。除此之外还会有 Service 相关类、Conf 配置文件、DB 连接等。
这些对象之间存在比较复杂的依赖关系,这就给项目的初始化带来了一些困扰。对于以上代码,对应初始化逻辑大概就会是这样:
我们会有一大段的逻辑都是用来做对象初始化,而当接口越来越多的时候,整个初始化过程就会异常的冗长和复杂。
针对以上问题,依赖注入可以完美的解决。
facebookgo/inject 的使用
接下来,我们试着使用 facebookgo/inject 的方式,对这段代码进行依赖注入的改造。如下:
inject:""inject.Graph{}graph.Provide()graph
Populate
serverconfdb
UserApiUserServicegraph
其实从下面这张对象依赖图能够很简单的看清楚。
confdbserver
inject:""
UserApiPostApiUserControllerPostControllerUserServiceConfUserServiceConf
以上就是整个依赖注入的流程了。
UserControllerPosterControllerUserService
我们的 main 函数使用 inject 进行改造后,将会变得非常简洁。而且即使随着业务越来越复杂,Handler 和 Service 越来越多,这个 main 函数中的注入逻辑也不会任何改变,除非有新的根节点对象出现。
当然,对于 Graph 来说,也不是只能 Provide 根节点和叶子节点,我们也可以自行 Provide 一个 UserService 的实例进去,对于 Graph 的运作是没有任何影响的。只不过只 Provide 根节点和叶子节点,代码会更简洁一些。
inject 的高级用法
inject:""inject:"private"inject:"inline"inject:"object_name"
private (私有注入)
我们上文讲过,默认情况下,所有的对象都是单例对象。一个类型只会有一个实例对象存在。但也可以不使用单例对象,private 就是提供了这种可能。
例如:
UserService
inject:""
但在实际开发中,这种 private 的场景似乎也比较少,大部分情况下,默认的单例对象就足够了。
inline (内联注入)
*Struct
*UserService
命名注入
inject
privateinline
同时,我们一定要把这个命名实例 Provide 到 graph 里面,这样 graph 才能把两个对象联系起来。
注入 map
我们除了可以注入对象外,还可以注入 map。如下:
inject:"private"
facebookgo/inject 的缺陷
inject:""
但是由于Golang本身的语言设计, facebookgo/inject 也会有一些缺陷和短板: 1. 所有需要注入的字段都需要是 public 的。 这也是 Golang 的限制,不能对私有属性进行赋值。所以只能对public的字段进行注入。但这样就会把代码稍显的不那么优雅,毕竟很多变量我们其实并不想 public。
- 只能进行属性赋值,不能执行初始化函数。 facebookgo/inject只会帮你注入好对象,把各个属性赋值好。但很多时候,我们往往需要在对象赋值完成后,再进行其他一些动作。但对于这个需求场景,facebookgo/inject并不能很好的支持。
这两个问题的原因总结归纳为:Golang没有构造函数......