Go 控制反转和依赖注入

当前问题

  • 当模块A依赖B,模块B依赖C,模块C依赖D,模块D依赖模块A时,GO编译就会报错 import cycle not allowed (循环依赖)

ioc

  • 多个模块就像多个齿轮一样共同协作完成任务,但是也可能会出现一个齿轮出现问题将会影响到整个齿轮组的运转的情况
  • 真实的服务可能需要依赖很多其他的服务,比如第三方,内部模块或者其他内部服务,当多个模块相互依赖增多时,也会增加项目的耦合,不利于后续功能拓展

object-dep

解决方法

控制反转

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

e2ce8ff5633d22eb11021febe7f61e93

由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,将控制权都交给了IOC,齿轮之间的转动全部依靠“第三方”,对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来

对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度

依赖注入

假设原本A依赖B,现在IOC需要new一个B出来,然后把B的实例注入到A里面去,这样A就可以正常使用基于B的方法了,这个过程被称为依赖项注入,而基于IOC的这种方法就叫做依赖注入

获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入

代码演示

当前场景

目前有一个创建文章Article的接口,分别做了http接口和gRPC接口。为了避免代码冗余,现在将具体逻辑放在gRPC中实现,而http接口将会调用gRPC函数来实现对应功能,而gRPC方法的调用需要创建相应的实例才能调用,下面是改造前的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// /apps/artcicle/http/http.go http接口
var service *Handler

type Handler struct {
svc article.Service //实现gRPC中article相关方法的接口
}

func Init() {
......

service = NewArtcileService() //在创建router前初始化gRPC实例并传进每一个接口中
r := NewRouter(service)

......
}

func NewRouter(s *ArticleService){
r := gin.Default()
r.POST("/article" ,s.createArticle) //传入gRPC实例
}

此时可以看出在http包中,创建router前,初始化了gRPC调用的实例,并将此实例传进了http具体的业务函数中,从而实现能在http接口中使用gRPC的函数,减少代码冗余的同时增加了http模块对gRPC模块的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// /apps/artcicle/http/article.go http接口
func (h *Handler) createArticle(c *gin.Context) {
......

rsp, err := h.svc.CreateArticle(c.Request.Context(), req) //调用gRPC方法
if err != nil {
response.Failed(ßerr)
return
}

......
}

// /apps/article/grpc/article.go grpc接口
func (a *ArticleService) CreateArticle(ctx context.Context, req *artcile.Article) (*article.Article, error) {
......

if err := a.dao.save(ctx, article); err != nil {
return nil, err
}

return article, nil
}

引入IOC

下面是使用控制反转和依赖注入的模式后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// /apps/ioc.go
import _ "xxx/apps/article/grpc" // 引入gRPC包,目的是为了使用其中的init方法为ioc容器中new一个article的grpc实例

var ArticleService Article.Service //实现gRPC中article相关方法的接口

func Register() {
...... //省略的逻辑:为http的Handler创建包含Config()的接口,然后在ioc中会有函数去将各个调用模块创建的接口会存进map ginApps中

for _, v := range ginApps {
v.Config() //主动将gRPC实例注入到调用模块中
}
}

// /apps/article/grpc/grpc.go grpc接口
func init() {
ioc.ArticleService = NewArticleService() // 为ioc容器中new一个grpc实例,则后续可以将该实例注入到其他模块中,就可以在其他模块中使用本模块的方法
}
1
2
3
4
5
6
7
8
9
10
// apps/artcicle/http/http.go http接口
var service *Handler

type Handler struct {
svc article.Service //实现gRPC中article相关方法的接口
}

func (h *Handler) Conifg() { //该方法被ioc容器调用
h.svc = apps.ArticleService //ioc中出初始化的gRPC实例注入到http模块中
}

从上面改进后的代码可以看出,之前的逻辑是是http模块依赖gRPC模块,使用IOC容器后,逻辑就变成了IOC容器初始化gRPC模块的实例,然后再将该实例注入到http模块中,解除了http模块和gRPC模块之间的耦合

总结

  • 使用控制反转和依赖注入可以减少项目的耦合度,有利于后续功能的拓展

  • 项目中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,现在又凭空多出一道手续,会使得项目变得不太直观


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!