定义
依赖反转包括两部分:控制反转、依赖注入
控制反转
先看下面这段代码
type UserServiceTest struct{}
func (t *UserServiceTest) DoTest() bool {
println("test logic")
return true
}
func ApplicationMain() {
u := new(UserServiceTest)
if u.DoTest() {
println("test success")
} else {
println("test fail")
}
}
上面这段程序的执行流程都是由程序员自己控制的,我们抽象出下面这样一个简单的测试框架,看看如何利用框架实现相同的功能
type TestCase interface {
DoTest() bool
}
type JunitTest struct {
testCaseList []TestCase
}
func (t *JunitTest) AddTestCase(testCase TestCase) {
t.testCaseList = append(t.testCaseList, testCase)
}
func (t *JunitTest) Run() {
for i := range t.testCaseList {
if t.testCaseList[i].DoTest() {
println("test success")
} else {
println("test fail")
}
}
}
type UserServiceTestCase struct{}
func (c *UserServiceTestCase) DoTest() bool {
println("test logic")
return true
}
func ApplicationMainV2() {
junitTest := new(JunitTest)
junitTest.AddTestCase(new(UserServiceTestCase))
junitTest.Run()
}
程序员就只需要定义自己的TestCase实现DoTest方法即可,TestCase的执行以及结果判断这些流程都交给框架来做
控制反转:就是将程序执行流程的控制权由程序员反转给框架
依赖注入
不通过new的方式在内部创建依赖类的对象,而是在外部创建好依赖类的对象后将对象传到内部使用
好处
- 提升代码的可扩展性,可以灵活替换依赖的类对象
- 提升代码的可测试性
依赖注入框架
一般我们的项目中可能有上百个类,如果这些类都需要我们去创建、注入那么成本很大,而对象创建、注入这些功能和业务逻辑无关。所以我们可以抽象一个单独的框架,基于框架提供的扩展点简单配置一下需要的类以及类之间的依赖关系,就可以由框架来负责创建对象、注入对象。
依赖反转
高层模块不要依赖低层模块,高层模块和低层模块通过抽象相互依赖,抽象不要依赖具体实现细节,具体实现细节依赖抽象
这里的高层模块可以理解为调用者,低层模块可以理解为被调用者
举例
这里拿Tomcat这个Servlet容器举个例子
Tomcat是运行Java Web程序的容器,Tomcat会调用Java Web程序,即Tomcat是高层模块,Web程序是低层模块,但是Tomcat并不依赖Web程序而是依赖Servlet规范,Web程序也依赖Servlet规范,所以高层模块和低层模块通过抽象相互依赖,Servlet规范不依赖Tomcat容器、Web程序的具体实现,Tomcat容器、Web程序的具体实现依赖Servlet规范