- 什么是依赖注入
- 依赖注入的好处
- Go的依赖注入-wire
依赖注入是什么?
第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前没学过Java和spring,所以第一次接触这个词的时候是很懵的。
依赖注入,英文名dependency injection,简称DI。依赖两个字很好理解,在软件设计上,从架构模块到函数方法都存在大大小小的依赖关系。
比如说在new A 之前需要先new B ,A依赖于B,这时候我们就可以说B是A的依赖,A控制B,AB之间存在着耦合的关系,而代码设计思想是最好可以做到松耦合。如果某一天B需要改造那么A也需要跟着改造。这是一个依赖你可以觉得没问题,但如果是A->B->C->D->E->F之间存在一连串的依赖关系,那么改造起来就会十分麻烦。
这个时候就需要一种东西来解开他们之间的强耦合,怎么解耦呢,只能借助第三方力量了,我们把A对B的控制权交给第三方,这种思想就称为控制反转(IOC Inversion Of Control),这个第三方称为IOC容器。而IOC容器要做的事情就是new一个B出来,然后把这个B的实例注入到A里面去,然后A就可以正常的使用基于B的方法了,这个过程被称为依赖项注入,而基于IOC的这种方法就叫做依赖注入。
依赖注入的好处
明白了依赖注入的思想,应该也就明白了其带来的最大好处——解耦。
而解耦又能带来更多的好处:代码扩展性增强,代码的可维护性增强,更容易进行单元测试等等。
那么依赖注入如何实现呢?
Java中有以下几种方式:
- setter方法注入:实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
- 基于接口的注入:实现特定接口以供外部容器注入所依赖类型的对象。
- 基于构造函数的注入:实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
- 基于注解的注入:在代码里加上特定的关键字实现注入。
注解是最常见的方式,它像注释一样不被当做代码来执行,而是专门供别人阅读。但注释的读者完全是人类,而注解的主要读者除了人类之外还有框架或预编译器。
Go依赖注入-wire
wiregowire
wireProviderInjector
providerwirewireinjectorinjectorinjectorprovider
wirewire.goMission
//+build wireinject
package main
import "github.com/google/wire"
func InitMission(name string) Mission {
wire.Build(NewMonster, NewPlayer, NewMission)
return Mission{}
}
+buildgo buildwirewireinjectwire.go
wire.Build()MissionNewMission()MissionNewMission()MonsterPlayerMonsterNewMonster()PlayerNewPlayer()NewMonster()NewPlayer()wire
写完wire.go文件之后执行wire命令,就会自动生成一个wire_gen.go文件。
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func InitMission(name string) Mission {
player := NewPlayer(name)
monster := NewMonster()
mission := NewMission(player, monster)
return mission
}
可以看到wire自动帮我们生成了InitMission方法,此方法中依次初始化了player,monster和mission。之后在我们的main函数中就只需调用这个InitMission即可。
func main() {
mission := InitMission("dj")
mission.Start()
}
而在没用依赖注入之前,我们的代码是这样的:
func main() {
monster := NewMonster()
player := NewPlayer("dj")
mission := NewMission(player, monster)
mission.Start()
}
是不是简洁了很多。这里只有三个对象的初始化,如果是更多可能才会意识到依赖注入的好处。
比如:
wire.go文件:
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package di
import (
"github.com/google/wire"
)
//go:generate kratos t wire
func InitApp() (*App, func(), error) {
panic(wire.Build(dao.Provider, service.Provider, http.New, grpc.New, NewApp))
}
实现文件:
//dao
var Provider = wire.NewSet(New, NewDB, NewRedis)
//service
var Provider = wire.NewSet(New, wire.Bind(new(pb.Server), new(*Service)))
生成的wire_gen.go 文件:
func InitApp() (*App, func(), error) {
redis, cleanup, err := dao.NewRedis()
if err != nil {
return nil, nil, err
}
db, cleanup2, err := dao.NewDB()
if err != nil {
cleanup()
return nil, nil, err
}
daoDao, cleanup3, err := dao.New(redis, db)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
serviceService, cleanup4, err := service.New(daoDao)
if err != nil {
cleanup3()
cleanup2()
cleanup()
return nil, nil, err
}
engine, err := http.New(serviceService)
if err != nil {
cleanup4()
cleanup3()
cleanup2()
cleanup()
return nil, nil, err
}
server, err := grpc.New(serviceService)
if err != nil {
cleanup4()
cleanup3()
cleanup2()
cleanup()
return nil, nil, err
}
app, cleanup5, err := NewApp(serviceService, engine, server)
if err != nil {
cleanup4()
cleanup3()
cleanup2()
cleanup()
return nil, nil, err
}
return app, func() {
cleanup5()
cleanup4()
cleanup3()
cleanup2()
cleanup()
}, nil
}
所以,依赖注入到底是什么?
封装解耦罢了。