今天我们来聊聊S.O.L.I.D原则
中的D:Dependence Inversion Principle(DIP)
,依赖倒置原则。
DIP 简介
依赖倒转原则 (DIP)
在整个S.O.L.I.D原则
是最为重要的,但偏偏又是最难理解的😓
尤其是它的定义:
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。
官方翻译:
高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。 抽象不应该依赖于细节。细节应该依赖于抽象。
说说这里面的两层意思:
1)高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。换言之,模块间的依赖是通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
2)接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。这一点其实不用多说,很好理解,“面向接口编程”思想
正是这点的最好体现。
一句话:一个特定的类不应该直接依赖于另外一个类,但是可以依赖于这个类的抽象(接口)。
被“倒置”的依赖是怎么回事儿呢?这里“倒置”的英文是"Inversion"
。大家是不是记得Spring中有个“控制反转”,英文是"Inversion of Control"
。它们在表达上很相似。Spring 中的对象由Spring容器来创建;相对的,依赖倒置中的依赖由抽象类来提供。也就是说,通过抽象类来拿依赖!
ps:我不知道这样讲,大家是否能明白!就类似说,依赖不能是new出来的具体实现,就好比Spring中一般不自己new一个对象一样。如果大家还是不理解,或者说不懂Spring中的CI,请记住”依赖抽象“就好。我们在稍后会讲为什么
DIP 示例
来个简单的例子:小明去上学,需要依赖交通工具,有自行车、地铁和小汽车。
🆗,让我们来把依赖的工具先实现出来.
public class Bike{
void run(){
System.out.println("骑自行车!");
}
}
public class Subway{
void run(){
System.out.println("乘地铁!");
}
}
public class Car{
void ride(){
System.out.println("开小汽车!");
}
}
好了,小明要去上学了,用什么工具呢?先用自行车吧!
public class XiaoMing {
Bike bike;
public XiaoMing(Bike bike) {
this.bike = bike;
}
public void goToSchool() {
this.bike.run();
}
}
非常完美!小明骑着自行车上学啦~
第二天,小明想做地铁去上学。
public class XiaoMing {
Subway subway;
public XiaoMing(Subway subway) {
this.subway = subway;
}
public void goToSchool() {
this.subway.run();
}
}
没办法,我们只能把依赖改为 Subway。
第三天,小明想开车去上学。我们还得改 XiaoMing 这个类。
public class XiaoMing {
Car car;
public XiaoMing(Car car) {
this.car = car;
}
public void goToSchool() {
this.car.run();
}
}
假如,突然小明家里很有前了,准备开飞机去上学。那就得新增一个 Plane 类,并再次修改 XiaoMing 类...
大家会发现:交通工具这个依赖的更换,就会导致对 XiaoMing 类的修改!
有没有办法能让依赖的更换不影响 XiaoMing 类呢?
有!依赖倒置原则派上用场。
依赖倒置原则,不是要要求依赖抽象吗?那我们就创建一个抽象类。
public interface TranspotTool{
void run();
}
那 XiaoMing 类就依赖于它。
public class XiaoMing {
TranspotTool tool;
public XiaoMing(TranspotTool tool) {
this.tool = tool;
}
public void goToSchool() {
this.tool.run();
}
}
好了,回到最开始的场景:小明骑自行车上学。需要把 Bike 依赖进来,但现在的依赖是需要 TranspotTool 类型。那么你的 Bike 就必须是 TranspotTool 类型。于是:
public class Bike implements TranspotTool{
@Override
void run(){
System.out.println("骑自行车!");
}
}
让我们来试一下:
public class Test {
public static void main(String[] args) {
TranspotTool tool = new Bike();
XiaoMing xiaoMing = new XiaoMing(tool);
xiaoMing.goToSchool();
}
}
输出:
骑自行车!
同样,我们可以这样修改 Subway 和 Car
public class Subway implements TranspotTool{
@Override
void run(){
System.out.println("乘地铁!");
}
}
public class Car implements TranspotTool{
@Override
void ride(){
System.out.println("开小汽车!");
}
}
再来测试下:
public class Test {
public static void main(String[] args) {
TranspotTool tool = new Car();
XiaoMing xiaoMing = new XiaoMing(tool);
xiaoMing.goToSchool();
}
}
输出:
开小汽车!
现在更换不用的交通工具时,不在需要修改 XiaoMing 类。
这里最明显的好处是:一个类依赖于抽象后,抽象类的衍生类的变化或替换不影响该类的业务代码,维护和扩展起来方便太多啦~
把上面的整个流程梳理一遍,发现抽象的 TranspotTool 接口才是依赖的核心。它把 XiaoMing 类和具体的交通工具 Bike, Subway, Car 等具体实现解耦啦。哪怕你再弄出十个、百个的其他具体交通工具,都不会影响到 XiaoMing 类。”松耦合“体现得非常的好。
总结
依赖倒置,就是可以通过抽象使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合(也是本质)
当我们应用这个原则的时候,我们能减少对特定实现的依赖性,让我们的代码复用性更高。
这就是为啥建议我们先写抽象类,由抽象类来提供依赖啦!
这样扩展性好呀!只要是这个抽象类的衍生类都可以替换。 ps:这里提到的“替换”是不是和我们之前提到过的里氏原则有点吻合呀!
还是之前提过的老话:S.O.L.I.D原则
中的各个原则间是相互有关联的。
具体的关联关系,小二哥会在后续的文章中分享!今天就先到这儿啦~