生活中的例子:
电脑的品牌有很多,但电脑中的所有部件都有标准的接口,不同的厂家都是按照标准去生产各个部件,这些部件的内部实现不同,但接口都是一样的,这样的话,如果我的联想电脑的内存条坏了,我随便买一根内存条插上都能使用。
之所以我们的电脑能使用任何厂家生产的内存条,是因为这些电脑生产厂家都是“面向接口”生产,而在软件世界中,面向接口编程就是依赖倒转。
依赖倒转的官方定义:
1.高层模块不应该依赖于低层模块,他们都应该依赖于抽象;
2.抽象不应该依赖于细节。细节应该依赖于抽象。
大白话讲:
高层模块:调用模块;低层模块:被调用模块。
高层模块中引用类型的变量不应该指向某一个具体的实现类,而应该指向一个抽象类或接口。
低层模块应该是一个接口/抽象类的实现类/子类。
面向实现编程的弊处:
如果是面向实现编程,即高层模块调用低层具有实现的函数,那么当高层模块想要换一种实现的时候,就需要修改高层模块的代码,让它调用另外一个具有实现的函数。这里由于修改了客户端的代码,所以破坏了“封闭修改”的原则。
面向接口编程的好处:
若高层模块一开始调用的是接口中的抽象函数,那么当高层类需要换一个函数实现的时候,高层类就不需要修改任何代码,在低层模块中创建一个实现接口的类,并重写接口中的函数,然后在高层类/低层类中创建实现类的对象并赋给抽象接口的引用即可。
高层类想要换一种实现,实现可以在高层类中换,也可以在低层类中换,最好的做法是在配置文件中换。
1.高层类中换实现
当低层类创建完一个新的实现类之后,在高层类中需要调用实现类函数的地方作如下修改:
接口 接口对象 = new 旧的实现类();——>接口 接口对象 = new 新的实现类();
这种方式在客户端仍然需要修改。
2.在低层类中换实现
低层类中需要再创建一个工厂,接口引用究竟指向哪个实现类的对象就由工厂类决定:
class Factory(){
接口 getBean(){
return new 旧的实现类();
}
}
class Factory(){
接口 getBean(){
return new 新的实现类();
}
}
3.在配置文件中更换实现
Spring采用了一种更加科学的方式,在配置文件中配置接口对应的实现类是哪一个。这样在程序运行的过程中,首先会读取配置文件,将用户指定的那个实现类的对象赋给接口引用。
如果用户想修改一种实现,只需在配置文件中换一下即可。