Go 的 DI 库 google/wire
什么是DI
DI(Dependency Injection)是一种设计模式,其中对象所依赖的对象不是自己准备的,而是从外部传递(从外部注入)。
例:考虑一个结构体,它有一个方法,给定一个导演的名字,返回一个包含该导演所有电影的列表:
func (ml *MovieLister) MoviesDirectedBy(director string ) []Movie {
allMovies := ml.finder.FindAll()
result := make ([]Movie, 0 , len (allMovies))
for _, m := range allMovies {
if director == m.Director {
result = append (result, m)
}
}
return result
}
该结构在名为 finder 的字段上具有 FindAll() 方法
type MovieLister struct {
finder MoviesFinder
}
type MoviesFinder interface {
FindAll() []Movie
}
此查找器将由 MovieLister 在正常控制流中初始化和设置。
func NewMovieLister() *MovieLister {
return &MovieLister{
finder: NewColonDelimitedMovieFinder( "movies.txt" ),
}
}
由此,MovieLister 与特定的 Finder 紧密结合。为了能够检索数据,无论它是在 RDB 中还是在外部 API 中,都需要在 MovieLister 外部初始化 Finder 并将其传递给 MovieLister。
func NewMovieLister(finder MoviesFinder) *MovieLister {
return &MovieLister{
finder: finder,
}
}
func main() {
finder := NewColonDelimitedMovieFinder( "movies.txt" )
ml := NewMovieLister(finder)
fmt.Println(ml.MoviesDirectedBy( "詹姆斯·卡梅隆" )
}
这样,使用 DI 模式的好处是让代码依赖关系更清晰,更灵活。
wire的使用方法
本地安装wire: go get github.com/google/wire/cmd/wire
准备一个定义依赖项的文件:
//+ wireinject
package main
import "github.com/google/wire"
func initMovieLister(fileName string ) *MovieLister {
wire.Build(
NewMovieLister,
NewColonDelimitedMovieFinder,
)
return nil
}
重要的//+build wireinject
是第 1 行的构建标记。这将在正常构建期间将 wire.go 从构建中排除。
此外,wire.Build
在函数参数中枚举Provider。wire 检查这些函数的签名以解决依赖关系。
这里使用的Provider如下:
func NewColonDelimitedMovieFinder(fileName string ) MoviesFinder
func NewMovieLister(finder MoviesFinder) *MovieLister
wire 检查这些函数的签名并生成代码来解析所需的依赖关系。对于生成,请使用安装go get
再.wire
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func initMovieLister(fileName string) *MovieLister {
moviesFinder := NewColonDelimitedMovieFinder(fileName)
movieLister := NewMovieLister(moviesFinder)
return movieLister
}
这样生成的initMovieLister
在main等中可以正常调用
func main() {
ml := initMovieLister( "movies.txt" )
fmt.Println(ml.MoviesDirectedBy( "詹姆斯·卡梅隆" ))
}
请注意,wire.Build()
的参数没有特定顺序。即使改变,生成的结果也不会改变,比如:
wire.Build(
NewColonDelimitedMovieFinder,
NewMovieLister,
)
Provider错误处理
除了简单地返回值之外,Provider还可以返回错误和函数。例如,如果 NewColonDelimitedMovieFinder 返回错误,则为:
func NewColonDelimitedMovieFinder(fileName string) (MoviesFinder, error)
相应的initMovieLister
函数也应该返回一个错误
func initMovieLister(fileName string ) (*MovieLister, error ) {
wire.Build(
NewMovieLister,
NewColonDelimitedMovieFinder,
)
return nil , nil
}
错误处理也将在生成的代码中完成
func initMovieLister(fileName string ) (*MovieLister, error ) {
moviesFinder, err := NewColonDelimitedMovieFinder(fileName)
if err != nil {
return nil , err
}
movieLister := NewMovieLister(moviesFinder)
return movieLister, nil
}
对应的Injector
可以在 wire.go 中定义任何 Provider 函数。此处定义的提供程序将复制到 wire_gen.go使用。
例如,转到标准sql.Open()
函数。
func Open(driverName, dataSourceName string ) (*DB, error )
由于它不能直接与 wire 一起使用,因此请在 Injector 中为 sql.Open 准备一个包装器。
type DriverName string
type DataSourceName string
func provideDBConn(driver DriverName, dsn DataSourceName) (*sql.DB, error) {
return sql.Open(string(driver), string(dsn))
}
func initDBConn(driver DriverName, dsn DataSourceName) (*sql.DB, error) {
wire.Build(
provideDBConn,
)
return nil, nil
}
在这定义了自己所需要的类型,使字符串可以按类型区分。如果您的数据库设置以类似 DBConfig 的结构组织,您可以将 DBConfig 作为参数传递给 provideDBConn 函数,并将字段传递给 sql.Open。
Provider Set
var movieListerSet = wire.NewSet(
NewMovieLister,
NewColonDelimitedMovieFinder,
)
wire.Build(
movieListerSet,
)
Set的使用是可选的,其实不使用 Set 也可以解决依赖关系。只是 Set 可用于避免冲突,例如当包名称冲突并需要别名时。
接口绑定
在其原始实现中,NewColonDelimitedMovieFinder 返回 MoviesFinder 接口的值,但返回具体类型 (*ColonDelimitedMovieFinder) 是可以的。 但是,在这种情况下,我们需要使用 wire.Bind() 将 *ColonDelimitedMovieFinder 绑定到 MoviesFinder 接口。
func NewColonDelimitedMovieFinder(fileName string)*ColonDelimitedMovieFinder {}
func NewMovieLister(finder MoviesFinder) *MovieLister {}
wire.Build(
NewMovieLister,
NewColonDelimitedMovieFinder,
wire.Bind(new(MoviesFinder), new(*ColonDelimitedMovieFinder)),
)
这确保 *ColonDelimitedMovieFinder 被传递给请求 MoviesFinder 接口的Provider。
引用结构的字段
比如想将 Director.Name 传递给 NewMovie 的param
type Movie struct {
Director string
}
func NewMovie(director string) *Movie {
return &Movie{Director: director}
}
type Director struct {
Name string
}
func NewDirector(name string) *Director {
return &Director{Name: name}
}
在这种情况下使用 wire.FieldsOf()
func initMovie() *Movie {
wire.Build(
NewMovie,
NewDirector,
wire.FieldsOf(new(*Director), "Name"),
)
return nil
}
生成的代码 :
func initMovie() *Movie {
director := NewDirector()
string2 := director.Name
movie := NewMovie(string2)
return movie
}
注意事项
值和指针:
例如,如果一个 Provider 返回一个指针而另一个 Provider 取一个值,wire 将抛出类似“No provider found for ColonDelimitedMovieFinder”的错误。
func NewColonDelimitedMovieFinder(fileName string ) *ColonDelimitedMovieFinder
func NewMovieLister(finder ColonDelimitedMovieFinder) *MovieLister
返回值或参数类型错误
通常,go run
有时只需要传递入 main.go,但在使用 wire 时还需要传递 wire_gen.go,然后运行 :
$ go run main.go wire_gen.go
否则,wire_gen.go 中定义的 Injector 函数将是未定义的,将抛出错误。
发表评论
要发表评论,您必须先登录。