0%

DI容器

DI容器

了解设计的第一步:模型

理解一个模型的关键在于,要了解这个模型设计的来龙去脉,知道它是如何解决相应的问题。

1613203014663

耦合的依赖

DI(Dependency Injection)容器,解决的是组件创建和组装的问题。

例子:假设有一个AritcleService提供根据标题查询文章的功能。当然,数据需要持久化的,所以,这里还有一个ArticleRepository,用来与持久化数据打交道。

  • repository层:和DB进行连接
  • service层:调用repository
  • 调用层:操作全体的层

repository层

由于ArticleRepository需要和DB进行连接,一个直接的做法就是在ArticleRepository中增加一个字段来表示DB

1
2
3
4
5
6
7
8
9
10
11
12
package repository

type ArticleRepository struct{
db sql.DB
}

func NewArticleRepository() *ArticleRepository {
//连接DB
db, _ := sql.Open("mysql","root:@/database")
//生成的db实例来直接创建repository
return &ArticleRepository{*db}
}

service层

在ArticleService处理业务的过程中,需要用到ArticleRepository辅助它完成功能,也就是说,ArticleService要依赖于ArticleRepository。

一个直接的做法是在ArticleService中增加一个字段表示ArticleRepository。

1
2
3
4
5
6
7
8
9
10
11
12
package service

type ArticleService struct{
ar repository.ArticleRepository
}

func NewArticleService() *ArticleService {
//生成reposiotry
ar := repository.NewArticleRepository()
//生成的repository直接来创建service
return &ArticleService(*ar)
}

调用层

初始化service,固定的repository和db也会被生成。

1
2
3
4
5
6
package main

func main(){
as := service.NewArticleService()
//使用as进行各种操作
}

问题所在

一旦开始准备测试,就会发现,如果想要ArticleService跑起来,那就的让ArticleReposiotry也跑起来;要让ArticleRepository跑起来,那就得准备database连接,还得准备database里面的各种数据。

而在真实的项目中,构建一个对象可能还会牵扯到更多内容:

  • 根据不同的参数,创建不同的实现类对象,可能需要用到工厂模式。
  • 为了了解方法的执行时间,需要给被依赖的对象加上监控。
  • 依赖的对象来自于某个框架,不知道具体的实现类是什么

分离的依赖

最简单的办法就是把创建对象的过程拿出去。只留下与字段关联的过程

  • repository层变成这样

db的连接和实例创建在外部进行,NewArticleRepository中传入的是db实例。

1
2
3
4
5
6
7
8
9
10
11
package repository

type ArticleRepository struct {
db sql.DB
}

func NewArticleRepository(db *sql.DB) *ArticleRepository {
//不再创建db连接
//传入的db实例来直接创建repository
return &ArticleRepository{*db}
}
  • service层变成这样
1
2
3
4
5
6
7
8
9
10
package service

type ArticleService struct {
as repository.ArticleRepository
}

func NewArticleService(as repository.ArticleRepository) ArticleService{
//直接传入ArticleRepository实例来直接创建ArticleService实例
return &articleService{as}
}

Constructor Injection

构造函数注入。引入间接层(接口),即New方法返回接口类型,来解耦各个层之间的依赖关系。

  • repository层

把已经实例化的db作为参数传入NewArticleRepository。而且NewArticleRepository的return返回的是接口类型,service不直接依赖repository,而是去依赖repository定义好的接口,解耦了repository层和service层的依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package repository

type AriticleRepository interface {
CreateUser(ctx context.Context, user *model.User)
}

type articleRepository struct {
db sql.DB
}

func NewArticleRepository(db *sql.DB) AriticleRepository {
return &articleRepository{*db}
}

func (ur *articleRepository) CreateUser(ctx context.Context, user *model.User) {
ur.db.Query("INSERT INTO table名(列名1,列名2,……)")
}
  • service层

把已经实例化的ArticleRepository接口类型作为参数传入NewArticleService中,因此不再依赖repository。return类型为ArticleService的接口类型,因此也解耦了service层和最终的操作层的依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package service

type ArticleService interface {
CreateUser(ctx context.Context, user *model.User)
}

type articleService struct {
ur repository.ArticleRepository
}

func NewArticlerService(ur repository.ArticleRepository) ArticleService {
return &articleService{ur}
}

func (us *articleService) CreateUser(ctx context.Context, user *model.User) {
us.ur.CreateUser(ctx, user)
}
  • 调用层

在这里进行所有的对象创建和组装。

ar := repository.NewArticleRepository(db):可以接受任何db类型(mysql,redis,PostgreSQL等)

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main(){
db, err := sql.Open("mysql","root:@/database")
if err != nil{
panic(err.Error())
}
defer db.Close()

ar := repository.NewArticleRepository(db)
as := service.NewArticleService(ar)
}

然而,由于这里进行了所有的对象创建和组装,会导致代码很臃肿。因此,引入DI container,让所有对象的创建和组装都在一个container中完成,再从container中取出已经实例化的对象。

参考:sarulabs/di