当前位置:网站首页>go wire 依赖注入入门

go wire 依赖注入入门

2020-11-07 20:55:45 Leoython

什么是依赖注入(DI)

要理解什么是依赖注入首先我们要知道什么事控制反转

控制反转

控制反转是面向对象编程中的一种设计原则或者设计模式,可以用来降低代码之间的耦合度,例如如下代码, class A 中有一个属性的类型是class B,如果在不使用控制反转的模式时,class需要自己新建class B的实例,这就导致了耦合。为了解耦,就出现了控制反转的模式,即将依赖的获得反转,不再由使用方创建依赖,而是由外部系统注入。

class A {
    Entity B;
}

什么是依赖注入

依赖注入是一种标准的技术,用于通过灵活地向组件提供其工作所需的所有依赖关系来生成灵活且松耦合的代码

依赖注入是控制反转的一种实现方式,其他的实现方式还有依赖查找,他们之间的区别

  1. 依赖注入是在对象创建时将对象的依赖通过某种方式注入
  2. 依赖查找是指在需要时,调用对象可以通过的框架所提供的方法获得需要的依赖。

依赖注入的实现方式

依赖注入的方式主要有3种:

  1. 构造器注入
  2. setter注入
  3. 接口注入

目前业界把依赖查找的一些实现方式也当做依赖注入,例如服务定位器,所以他们之间的界限并不是很清晰

wire简介

wire是一种依赖注入工具,它采用的是构造器注入的方式,但是在是线上它并不是通过反射来实现的。在这篇blog中,go团队说明了为什么不采用反射的方式实现。主要的原因是考虑到反射的实现会把错误带到运行时并且是难以理解和调试的,而go团队更愿意在编译时就发现此问题。

实例

在示例代码中我们会有一个speaker和一个message,在speaker初始化时需要一个message,然后调用speakerSay方法打印出message的文字

无依赖注入

可以看到,在此版本中,speaker需要自己构造message,很明显,这就是一个耦合点,speaker的实现耦合了message的实现,接下来,让我们改造代码,将它变成构造器注入的模式。

package main

import "fmt"

type Message string

func NewMessage(text string) Message {
    return Message(text)
}

type Speaker struct {
    Message Message
}

func (s Speaker) Say() {
    fmt.Println(s.Message)
}

func NewSpeaker(text string) Speaker {
    m := NewMessage(text)
    return Speaker{
        Message: m,
    }
}

func main() {
    s := NewSpeaker("hello, I am a speaker")
    s.Say()
}

构造器注入

可以看到,这个版本跟无依赖注入的版本的差距只是message的实例不再是由speaker初始化,而是交由外部注入,这样的好处是我们可以有多个NewMessage的实现,具体注入的是哪个实现与speaker的实现解耦了。

这项技术在小规模时效果很好,但是较大的应用程序可能具有复杂的依赖关系图,从而导致一大堆初始化代码依赖于顺序,一般很难将代码清晰地分解,特别是因为某些依赖项被多次使用。将服务的一种实现替换为另一种实现可能会很痛苦,因为这涉及通过添加一组新的依赖关系,并删除未使用的旧依赖关系来修改依赖关系图。

package main

import "fmt"

type Message string

func NewMessage(text string) Message {
    return Message(text)
}

type Speaker struct {
    Message Message
}

func (s Speaker) Say() {
    fmt.Println(s.Message)
}

func NewSpeaker(m Message) Speaker {
    return Speaker{
        Message: m,
    }
}

func main() {
    m := NewMessage("hello, I am a speaker")
    s := NewSpeaker(m)
    s.Say()
}

wire版本

wire旨在简化初始化代码的管理。可以通过代码或配置来描述服务及其依赖关系,然后Wire处理生成的图形以弄清楚顺序以及如何将每个服务传递给它所需要的东西。通过更改函数签名或添加或删除初始化程序来更改应用程序的依存关系,然后让Wire做繁琐的工作来为整个依存关系图生成初始化代码。

让我们首先添加一个wire.go

注意顶部的构建约束,是用来告知go编译时不需要包括此文件

//+build wireinject
// The build tag makes sure the stub is not built in the final build.

package main

import "github.com/google/wire"

func InitializeSpeaker(text string) Speaker {
    wire.Build(NewSpeaker, NewMessage)
    return Speaker{}
}

然后让我们改造一下main.go

可以看到,speaker不再是使用NewSpeaker函数创建,而是使用InitializeSpeaker

package main

import "fmt"

type Message string

func NewMessage(text string) Message {
    return Message(text)
}

type Speaker struct {
    Message Message
}

func (s Speaker) Say() {
    fmt.Println(s.Message)
}

func NewSpeaker(m Message) Speaker {
    return Speaker{
        Message: m,
    }
}

func main() {
    s := InitializeEvent("hello, I am a speaker")
    s.Say()
}

那么,InitializeSpeaker 的实现是这么样的呢?我们可以在项目下执行wire命令,wire会自动给我们生成wire_gen.go文件, 可以看到,我们不需要自己编写实现,wire会自动识别依赖关系并为我们生成代码

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from wire.go:

func InitializeSpeaker(text string) Speaker {
    message := NewMessage(text)
    speaker := NewSpeaker(message)
    return speaker
}

最后

在此篇文章中并未提到wire的一些高级特性和概念,例如injector、provider、接口绑定等,有兴趣的同学可以查看wire的文档了解

版权声明
本文为[Leoython]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000037759204