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

耦合的依赖
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, _ := sql.Open("mysql","root:@/database") 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 { ar := repository.NewArticleRepository() return &ArticleService(*ar) }
|
调用层
初始化service,固定的repository和db也会被生成。
1 2 3 4 5 6
| package main
func main(){ as := service.NewArticleService() }
|
问题所在
一旦开始准备测试,就会发现,如果想要ArticleService跑起来,那就的让ArticleReposiotry也跑起来;要让ArticleRepository跑起来,那就得准备database连接,还得准备database里面的各种数据。
而在真实的项目中,构建一个对象可能还会牵扯到更多内容:
- 根据不同的参数,创建不同的实现类对象,可能需要用到工厂模式。
- 为了了解方法的执行时间,需要给被依赖的对象加上监控。
- 依赖的对象来自于某个框架,不知道具体的实现类是什么
分离的依赖
最简单的办法就是把创建对象的过程拿出去。只留下与字段关联的过程
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 { return &ArticleRepository{*db} }
|
1 2 3 4 5 6 7 8 9 10
| package service
type ArticleService struct { as repository.ArticleRepository }
func NewArticleService(as repository.ArticleRepository) ArticleService{ return &articleService{as} }
|
Constructor Injection
构造函数注入。引入间接层(接口),即New方法返回接口类型,来解耦各个层之间的依赖关系。
把已经实例化的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,……)") }
|
把已经实例化的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