google 出品的依赖注入库 wire:https://github.com/google/wire
什么是依赖注入
依赖注入 ,英文全名是 dependency injection,简写为 DI。
百科解释:
依赖注入是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。
在用编程语言编写程序时,比如用 java 语言,会编写很多类,这些类之间相互调用,完成一个具体的功能。
例如,从 MySQL 获取数据,那么需要一个 MySQL 操作类 。
第一次编写mysql操作类:
要从 mysql 获取数据,那么 mysql 数据库的用户名,密码,地址等等这些配置信息,也是需要的,继续编写 MySQL 类:
进一步思考,上面的 MySQL 操作类程序有什么不妥的地方?
编程原则里有一个原则就是:单一职责
也就是说一个类最好只干一件事情。
根据这个原则在看看 MySQL 类,里面有获取数据库配置数据,也有操作MySQL的方法,不是单一职责的。
那里面获取数据库配置数据,可不可以单独拎出来用一个类表示? 当然可以。
因为 MySQL 配置数据,多数是从文件里读取的,上面 MySQL 类是写死,这也是不合理的一个地方。
而配置文件的来源,可以是 yml 格式文件,也可以是 toml 格式文件,还可以是远程文件。
第二次编写mysql操作类:
修改上面的类,增加一个获取数据库配置的类:
获取数据的类变成:
思考一下,上面改写后的类有什么不妥的地方?
获取mysql的配置信息,是不是要在 MySQL 类里 new一下, 实例化一下,如果不在同一个包下,还要把配置类引入进来在才能实例化。这里能不能优化下,当然可以。
直接把数据库配置类注入到 MySQL 操作类里。这就是依赖注入。
- 依赖是什么?注入又是什么?
mysql 操作类依赖谁?依赖数据库配置类。
注入什么?把数据库配置类注入到 mysql 操作类里。
注入是一个动作,把一个类注入到另外一个类。
依赖是一种关系,类关系,一个类要完全发挥作用,需要依赖另外一个类。
要完成数据操作,mysql操作类是需要依赖数据库配置类的,把数据库配置类注入到mysql操作类里,就可以完成操作类功能。
第三次编写mysql操作类:
伪代码示例:
把数据库配置类注入到mysql操作类里。
写 java 的人都知道 java 框架里有一个 spring 全家桶,spring 框架包核心有2个,其中有一个核心就是 IoC,另一个是 aop。
IoC 的全称:Inversion of Control,控制反转。
这个控制反转也是面向对象编程原则之一。
但是这个控制反转比较难理解,如果结合上面的 DI 来理解,就比较容易理解点。
可以把 DI 看作是 IoC 编程原则的一个具体实现。
依赖注入还可以从另外的软件设计思想来理解:
- 分离关注点
- 高内聚,低耦合
对数据库 mysql 的操作和 mysql 的配置信息,这个 2 个是可以相互独立,相分离的。
何时使用依赖注入
当你的项目规模不大,文件不是很多,一个文件调用只需要传入少量依赖对象时,这时使用依赖注入就会使程序变得繁琐。
当规模变大,单个对象使用需要调用多个依赖对象时,而这些依赖又有自己依赖对象,这时对象创建变得繁琐,那么这时候依赖注入就可以出场了。
wire 概念说明
wire 简介
wire 是由 google 开源的一个用 Go 语言实现的依赖注入代码生成工具。它能够根据你写的代码生成相应的依赖注入 Go 代码。
与其他依赖注入工具不同,比如 uber 的 dig 和 facebook 的 inject,这 2 个工具都是使用反射实现的依赖注入,而且是运行时注入(runtime dependency injection)。
wire 是编译代码时生成代码的依赖注入,是编译期间注入依赖代码(compile-time dependency injection)。而且代码生成期间,如果依赖注入有问题,生成依赖代码时就会出错,就可以报出问题来,而不必等到代码运行时才暴露出问题。
provider 和 injector
首先,需要理解 wire 的 2 个核心概念:provider 和 injector。
从上面 java 模拟依赖注入的例子中,可以简化出依赖注入的步骤:
第一:需要 New 出一个类实例
第二:把这个 New 出来的类实例通过构造函数或者其他方式“注入”到需要使用它的类中
第三:在类中使用这个 New 出来的实例
从上面步骤来理解 wire 的 2 个核心概念 provider 和 injector。
provider 就相当于上面 New 出来的类实例。
injector 就相当于“注入”动作前,把所需依赖函数进行聚合,根据这个聚合的函数生成依赖关系。
provider:提供一个对象。
injector:负责根据对象依赖关系,生成新程序。
provider
provider 是一个普通的 Go 函数 ,可以理解为是一个对象的构造函数。为下面生成 injector 函数提供”构件“。
看下面例子,来自 go blog。
这篇 blog 是 2018.10.9 发表,可能一些信息有点老,再参考 github guide ,这篇 guide 最后更新于 2021.1.26。
下面的 NewUserStore() 函数可以看作是一个 provider。这个函数需要传入 *Config 和 *mysql.DB 2 个参数。
wire.NewSet
你也可以把其他的 provider sets 加入一个 provider set,
wire.NewSet() 函数:
这个函数可以把相关的 provider 组合在一起然后使用。当然也可以单独使用,如 var Provider = wire.NewSet(NewDB)。
这个 NewSet 函数的返回值也可以作为其他 NewSet 函数的参数使用,比如上面的 SuperSet 作为参数使用。
injector
wire
wire
例子如下:
wireinitUserStore
wire
wire 使用
wire 结构体和方法列表
更详细说明可以看这里 func index - pkg.go.dev。
wire 安装
快速开始
例子1
go mod init basicsrequire github.com/google/wire v0.5.0
整个文件夹目录结构:
定义 providers
在 basics 文件夹下新建 basics.go 文件,写入如下代码:
定义 injector
新建文件 wire.go,代码如下:
// +build wireinject
这一行代码一定要在包最上面声明,表明这是一个准备被编译的 injector
用 wire 命令生成 injector 函数代码
wirewire
报错了,看看显示出的错误信息,最主要是这一行信息:
inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet"
context.Contextcontext.Context
wire
生成的 injector 代码,wire_gen.go 文件,
小结
wire 使用的步骤:
先编写 provider。再编写 injector:把相关 provider 组织在一起,成为一个 ProviderSet。最后用 wire 命令编译:wire 会根据 provider 之间相关依赖生成代码。
wire.NewSet 函数:
它可以把 provider 集合起来。作用1分类:可以把一组相关的 provider 写在一起组成 ProviderSet。作用1延伸第2个作用,避免 provider 过多难于管理。
wite.Build 函数:
它的参数是 provider 不定长列表。 把所有相关的 provider 组织在一起然后生成 injector 函数代码。它是生成 injector 函数的模板函数。
绑定接口
上面例子1绑定的是结构体和构造函数。如果有接口 interface 参与呢,那怎么办?比如下面的代码,
有接口 Fooer,这个怎么绑定呢?这时候就可以用 [wire.Bind](wire/wire.go at v0.5.0 · google/wire · GitHub) 函数,
struct prividers
wire.Struct
更多信息请参考struct providers guide
Provider Set
上面例子1中就用到 provider set,把
wire.NewSet
更多例子请查看官方文档:
https://github.com/google/wire
参考
https://github.com/google/wire
https://github.com/google/wire/blob/main/docs/guide.md
https://go.dev/blog/wire
wire func index - pkg