什么是依赖注入(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 mainimport "fmt"type Message stringfunc 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 mainimport "fmt"type Message stringfunc 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 mainimport "github.com/google/wire"func InitializeSpeaker(text string) Speaker {    wire.Build(NewSpeaker, NewMessage)    return Speaker{}}

而后让咱们革新一下main.go

能够看到,speaker不再是应用NewSpeaker函数创立,而是应用InitializeSpeaker

package mainimport "fmt"type Message stringfunc 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 !wireinjectpackage main// Injectors from wire.go:func InitializeSpeaker(text string) Speaker {    message := NewMessage(text)    speaker := NewSpeaker(message)    return speaker}

最初

在此篇文章中并未提到wire的一些高级个性和概念,例如injector、provider、接口绑定等,有趣味的同学能够查看wire的文档理解