Golang 实现依赖注入

Golang 实现依赖注入

什么是依赖注入

依赖注入就是将实例变量传入到一个对象中去

为何要做依赖注入

让开发者从对项目中大量依赖的创建和管理中解脱出来

控制反转(IoC)与依赖注入(DI)

控制反转(Inversion of Control)是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。

控制反转和依赖注入的关系

我们已经分别解释了控制反转和依赖注入的概念。有些人会把控制反转和依赖注入等同,但实际上它们有着本质上的不同。

  • 控制反转是一种思想
  • 依赖注入是一种设计模式
  • 依赖注入可以作为实现控制反转的方式

测试程序的依赖关系

我们用来测试的程序的依赖关系

Golang 实现依赖注入_第1张图片

没有依赖注入之前

package main
 
import (
    "fmt"
    "log"
    "net/http"
)
 
type Config struct{
 
}
 
type DB struct {
  config *Config
}
 
type PersonRepositor struct {
    db *DB
}
 
type PersonService struct {
    personRepositor *PersonRepositor
}
 
type Server struct {
    personService *PersonService
}
 
func (s *Server) Run() {
    fmt.Print("Server Run\n")
    HTTPServer()
}
 
func NewConfig() *Config {
    fmt.Print("new Config\n")
    return new(Config)
}
func ConnectDatabase(c *Config) *DB {
    fmt.Printf("new DB, need config:%+v\n", c)
    return new(DB)
}
 
func NewPersonRepository(db *DB) *PersonRepositor {
    fmt.Printf("new PersonRepositor, need DB:%+v\n", db)
    return new(PersonRepositor)
}
 
func NewPersonService(personRepositor *PersonRepositor) *PersonService {
    fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personRepositor)
    return new(PersonService)
 
}
 
func NewServer(personService *PersonService) *Server {
    fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personService)
    return new(Server)
}
 
func main() {
    config := NewConfig()
    db := ConnectDatabase(config)
    personRepository := NewPersonRepository(db)
    personService := NewPersonService(personRepository)
    server := NewServer(personService)
    server.Run()
 
}
 
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello there!\n")
}
 
func HTTPServer() {
    http.HandleFunc("/", myHandler) //  设置访问路由
    log.Fatal(http.ListenAndServe(":8080", nil))
}
  • 我们首先创建Config,然后使用Config创建数据库连接。接下来我们可以创建PersonRepository,这样就可以创建PersonService了,最后,我们使用这些来创建Server并启动它。现在这个简单的程序看起来还是不是复杂,但是当我们的程序依赖复杂起来的时候,整个手动依赖注入的就会显得非常多,而且复杂。想象一下,如果项目中有上百个依赖,那么就会有上百行的 New...(...) 代码。

  • 这个时候,你就会想,是不是有框架自动做这些依赖的注入。事实上,确实有人做了这样的事情,比如 uber.org/dig、facebookgo/inject 、google/wire。接下来,我们将简单看下如何使用这些框架来帮助我们实现自动的依赖注入

Golang 实现依赖注入的几种方式

uber.org/dig、facebookgo/inject 、google/wire

使用反射

uber.org/dig 对 Go 项目进行依赖注入

使用方式

package main
 
import (
    "fmt"
    "log"
    "net/http"
 
    "go.uber.org/dig"
)
 
type Config struct{
    Name string `json:"name"`
}
 
type DB struct {
    Config *Config
}
 
type PersonRepositor struct {
    DB *DB
}
 
func (p PersonRepositor) Hello(){
    fmt.Printf("hello, test for:%s\n", p.DB.Config.Name)
}
 
type PersonService struct {
    PersonRepositor *PersonRepositor
}
 
type Server struct {
    PersonService *PersonService
}
 
 
func (s *Server) Run(){
    fmt.Print("Server Run\n")
    s.PersonService.PersonRepositor.Hello()
    HTTPServer()
}
 
func NewConfig() *Config{
    fmt.Print("new Config\n")
    c := new(Config)
    c.Name = "go.uber.org/dig"
    return c
}
 
func ConnectDatabase(c *Config) *DB {
    fmt.Printf("new DB, need config:%+v\n", c)
    db := new(DB)
    db.Config = c
    return db
}
 
func NewPersonRepository(db *DB) *PersonRepositor {
    fmt.Printf("new PersonRepositor, need DB:%+v\n", db)
    p := new(PersonRepositor)
    p.DB = db
    return p
}
 
func NewPersonService(personRepositor *PersonRepositor) *PersonService {
    fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personRepositor)
    p := new(PersonService)
    p.PersonRepositor = personRepositor
    return p
 
}
 
func NewServer(personService *PersonService) *Server {
    fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personService)
    s := new(Server)
    s.PersonService = personService
    return s
}
 
func BuildContainer() *dig.Container {
    container := dig.New()
 
    container.Provide(NewConfig)
    container.Provide(ConnectDatabase)
    container.Provide(NewPersonRepository)
    container.Provide(NewPersonService)
    container.Provide(NewServer)
 
    return container
}
 
func main() {
    container := BuildContainer()
    err := container.Invoke(func(server *Server) {
        server.Run()
        HTTPServer()
    })
 
    if err != nil {
        panic(err)
    }
}
 
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello there!\n")
}
 
func HTTPServer() {
    http.HandleFunc("/", myHandler) //  设置访问路由
    log.Fatal(http.ListenAndServe(":8080", nil))
}

如何使用

从上面的代码我们可以看到,用dig 来实现依赖注入。

  1. 我们只需要将我们的被依赖方的创建方式注册到Provide中
  2. 我们的服务在启动的时候,只需要在Invoke中启动就可以了
  • 更多使用方式:https://pkg.go.dev/go.uber.org/dig#section-documentation

facebookgo/inject 对 Go 项目进行依赖注入

使用方式

package main
 
import (
    "fmt"
    "log"
    "net/http"
 
    "github.com/facebookarchive/inject"
)
 
type Config struct{
    Name string `json:"name"`
}
 
type DB struct {
    Config *Config `inject:""`
}
 
type PersonRepositor struct {
    DB *DB `inject:""`
}
 
func (p PersonRepositor) Hello(){
    fmt.Printf("hello, test for:%s\n", p.DB.Config.Name)
}
 
type PersonService struct {
    PersonRepositor *PersonRepositor `inject:""`
}
 
type Server struct {
    PersonService *PersonService `inject:""`
}
 
 
func (s *Server) Run(){
    fmt.Print("Server Run\n")
    s.PersonService.PersonRepositor.Hello()
    HTTPServer()
}
 
func NewConfig() *Config{
    fmt.Print("new Config\n")
    c := new(Config)
    c.Name = "facebookgo-inject"
    return c
}
 
func main() {
    graph := inject.Graph{}
    conf := NewConfig()
    server := Server{}
    if err := graph.Provide(
        &inject.Object{
            Value: &server,
        },
        &inject.Object{
            Value: conf,
        },
    ); err != nil {
        panic(err)
    }
 
    if err := graph.Populate(); err != nil {
        panic(err)
    }
 
    server.Run()
}
 
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello there!\n")
}
 
func HTTPServer(){
    http.HandleFunc("/", myHandler)     //  设置访问路由
    log.Fatal(http.ListenAndServe(":8080", nil))
}

如何使用

  1. 首先每一个需要注入的字段都需要打上 inject:“” 这样的 tag。所谓依赖注入,这里的依赖指的就是对象中包含的字段,而注入则是指有其它程序会帮你对这些字段进行赋值。
  2. 其次,我们使用 inject.Graph{} 创建一个 graph 对象。这个 graph 对象将负责管理和注入所有的对象。至于为什么叫 Graph,其实这个名词起的非常形象,因为各个对象之间的依赖关系,也确实像是一张图一样。
  3. 接下来,我们使用 graph.Provide() 将需要注入的对象提供给 graph。
  4. 最后调用 Populate 函数,开始进行注入。

文档参考

  • Facebook 在 Golang 依赖注入的实现
  • [译]Golang中的依赖注入
  • 使用 google/wire 对 Go 项目进行依赖注入
  • Golang | wire库

你可能感兴趣的:(Golang,golang)