GO DI 分析

769 阅读3分钟

背景

在开发过程中或者写单测过程中,经常遇到这种情况,为了初始化类A而需要初始化类A中所以依赖的类。

例如,类A中有一个属性是类B,则在初始化类A的时候,需要初始化一个类B的实例,这种操作导致类A和类B耦合太紧,不方便解耦,尤其是测试的类A的时候不方便。当类A中依赖了多个类,并且需要初始化多次类A的时候,不方便的地方更明显。

为了解决这个问题,大家想到一种方法,在初始化类A的时候,可以通过注入的方式来初始化类B,而且尽量简洁、优雅、开发者少感知,这种方法就是依赖注入。

名词解释

  • DI:Dependency Injection,依赖注入,是一种设计模式。其作用是去除类之间的依赖关系,实现松耦合,以便于开发测试

fx

github.com/uber-go/fx

Fx是uber开业的一个golang版本的依赖注入框架,它使得golang通过可重用、可组合的模块化来构建golang应用程序变得非常容易。

示例代码

package main

import (

    "context"

    "fmt"

    "github.com/uber-go/fx"

)


func main() {

    type t3 struct {

        Name string

        t4 t4

    }



    type t4 struct {

        Age int

    }



    var (

        v1 *t3

        v2 *t4

    )



    app := fx.New(

        fx.Provide(func() *t3 { return &t3{"hello everybody!!!"} }),

        fx.Provide(func() *t4 { return &t4{2019} }),



        fx.Populate(&v1),

        fx.Populate(&v2),

    )



    app.Start(context.Background())

    defer app.Stop(context.Background())



    fmt.Printf("the reulst is %v , %v\n", v1.t4.Name, v2.Age)

}

通过将构造函数传入到fx中,然后调用Populate完成变量之间依赖关系的映射。

dig

pkg.go.dev/go.uber.org…

dig 是 uber 开源的依赖注入库。

示例代码

package main



import (

  "fmt"



  "github.com/jessevdk/go-flags"

  "go.uber.org/dig"

  "gopkg.in/ini.v1"

)



type Option struct {

  ConfigFile string `short:"c" long:"config" description:"Name of config file."`

}



func InitOption() (*Option, error) {

  var opt Option

  _, err := flags.Parse(&opt)



  return &opt, err

}



func InitConf(opt *Option) (*ini.File, error) {

  cfg, err := ini.Load(opt.ConfigFile)

  return cfg, err

}



func PrintInfo(cfg *ini.File) {

  fmt.Println("App Name:", cfg.Section("").Key("app_name").String())

  fmt.Println("Log Level:", cfg.Section("").Key("log_level").String())

}



func main() {

  container := dig.New()



  container.Provide(InitOption)

  container.Provide(InitConf)



  container.Invoke(PrintInfo)

}

使用dig的一般流程:

  • 创建一个容器:dig.New
  • 为想要让dig容器管理的类型创建构造函数,构造函数可以返回多个值,这些值都会被容器管理;
  • 使用这些类型的时候直接编写一个函数,将这些类型作为参数,然后使用container.Invoke执行我们编写的函数。

这是通过函数传参的形式实现依赖注入,它也可以自动初始化结构体的属性。在结构体中加入dig.In之后,结构体中的类属性就会自动调用初始化函数来初始化。

命名依赖实现同一个类有多个实例

type MongodbClient struct {}

func NewMongoClient(cfg *Mongo.Client) *MongodbClient{

    return ConnectionMongo(cfg)

}



type MongodbClients struct {

    dig.Out

    

    Mgo1 *MongodbClient `name:"mgo1"`

    Mgo2 *MongodbClient `name:"mgo2"`

}

container.Provide(NewConfig, dig.Name("mgo1"))

container.Provide(NewMongoClient, dig.Name("mgo1"))

container.Provide(NewMongoClient, dig.Name("mgo2"))



m MongodbClients

wire

github.com/google/wire…

google的一个开源依赖注入框架

使用wire前

func CreateConcatService() *ConcatService {

        logger := &Logger{}

        client := NewHttpClient(logger)

        return NewConcatService(logger, client)

}

使用wire后

func CreateConcatService() *ConcatService {

        panic(wire.Build(

                wire.Struct(new(Logger), "*"),

                NewHttpClient,

                NewConcatService,

        ))

}
$ go get github.com/google/wire/cmd/wire

$ wire

生成wire_gen.go文件

func CreateConcatService() *ConcatService {

        logger := &Logger{}

        httpClient := NewHttpClient(logger)

        concatService := NewConcatService(logger, httpClient)

        return concatService

}

inject

pkg.go.dev/github.com/…

Facebook 开源的依赖注入框架

示例代码

package main



import (

    "fmt"

    "os"



    "github.com/facebookgo/inject"

)



type Animal struct {

    Cat *Cat `inject:""`

    Dog *Dog `inject:""`

}



func (a *Animal) Detail() string {

    return fmt.Sprintf("Cat:%s\nDog:%s", a.Cat.Detail(), a.Dog.Detail())

}



type Cat struct {

    Name     string

    Age      int

    Roommate *Dog `inject:"object_name"`

}



func (c *Cat) Detail() string {

    return fmt.Sprintf("i am cat. my name is %s, my age is %d, i have a roommate:%s", c.Name, c.Age, c.Roommate.GetName())

}



type Dog struct {

    Name string

    Age  int

}



func (d *Dog) Detail() string {

    return fmt.Sprintf("i am dog. my name is %s, my age is %d", d.Name, d.Age)

}



func (d *Dog) GetName() string {

    return d.Name

}



func main() {

    var g inject.Graph

    var a Animal

    err := g.Provide(

        &inject.Object{Value: &a},

        //&inject.Object{Value: &Cat{Name: "cat1", Age: 8}},

        &inject.Object{Value: &Dog{Name: "dog1", Age: 9},Name:"xx"},

    )

    if err != nil {

        fmt.Fprintln(os.Stderr, err)

        os.Exit(1)

    }



    if err := g.Populate(); err != nil {

        fmt.Fprintln(os.Stderr, err)

        os.Exit(1)

    }



    fmt.Println(a.Detail())

}



// output:

Cat:i am cat. my name is cat1, my age is 8, i have a roommate:dog1

Dog:i am dog. my name is dog1, my age is 9

inject 轻量级DI框架,代码不足700行。它提前将有依赖关系的对象的实例提供好,然后调用Populate设置对象之间的依赖关系。

比较

image.png

参考

[1] pkg.go.dev/github.com/…

[2]www.jianshu.com/p/847e1c8d2…

[3]studygolang.com/articles/18…

[4]zhuanlan.zhihu.com/p/67032669

[5]go.dev/blog/wire

[6]juejin.cn/post/693379…

[7]www.cyhone.com/articles/fa…

[8]juejin.cn/post/693379…

[9]zhuanlan.zhihu.com/p/108518676

[10]juejin.cn/post/689851…

[11]stackoverflow.com/questions/4…

[12]github.com/google/wire…

[13]blog.drewolson.org/go-dependen…

[14]jerryzhou343.github.io/post/di/