2022-09-21 依赖倒置
《设计模式之禅》之依赖倒置原则
,Dependence Inversion Principle(DIP),核心就是面向接口编程,而不是面向实现编程。
高层模块不应该依赖低层模块,两者都应该依赖其抽象。
接口或抽象类不依赖于实现类
实现类依赖接口或抽象类
public class Driver {
//司机的主要职责就是驾驶汽车
public void drive(Benz benz){
benz.run();
}
}
书中的例子,司机 Dirver 类内部用了奔驰汽车 Benz 类,这就是具体的类支持相互依赖,耦合高,如果还有一个宝马汽车 BMW 类,由于 drive 定死了参数,导致无法使用 BMW 对象。
所以应该抽象一个 ICar 接口,让 Benz 和 BMW 实现它,drive 的参数用 ICar。这还不彻底,还是具体的类 Driver,所以还抽象一个 IDriver 接口,变成纯接口之间依赖。
public interface IDriver {
//是司机就应该会驾驶汽车
public void drive(ICar car);
}
还有个好处就是多人并行开发,先定义好接口,实现各干各的,彼此不影响进度。
public class Client {
public static void main(String[] args) {
private IDriver zhangsan = new Driver();
private ICar benz = new Benz();
zhangsan.drive(benz)
}
}
一开始真没看出来有什么作用,下面的代码和上面的代码能有什么区别?
Driver zhangsan = new Driver();
Benz benz = new Benz();
zhangsan.drive(benz);
说 Client 作为更高层的业务,作为高层模块不应该依赖低层模块,所以不应该直接用 Driver,Benz 这种具体的实现,而是要用抽象的 IDriver,ICar,zhangsan 都当作 IDriver 用,屏蔽了 Driver 细节的影响,这里大概明白了原因。
假设 zhangsan 这个 Driver 不仅可以 drive(ICar)
,还可以 fly(IPlane)
,现在 Client 这种高层业务显然只想用他作为 IDriver 的通用功能,即 drive,别的具体的实现能飞,还是能潜水,对于 Client 来说,不想知道,不想关注。如果写成 Driver zhangsan
,那么可能后来有个人接手代码了,说原来 zhangsan 可以飞呀,那干吗还要开车这么慢,直接开飞机了。如果业务后面有个判断说如果行驶距离超过50公里了就强制休息一下,如果用 Driver,然后有人用 fly,那可能就坠机了,但如果用 IDriver,接口里只有 drive,用的人也只能开车,要休息那就在路上停一会就行。
不知是不是这么理解的?
后来讲三种对象注入方法,构造函数、setter 方法、上面的接口方式,想到依赖注入框架 Dragger,在那看到过这些注入介绍,嫌弃这么注入不好,用注解做。
最佳实践:
-
每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。这个倒是可以有,而且过去好像并没有这个主动意识。
-
变量的表面类型尽量是接口
像上面例子的 zhangsan,声明他尽量用接口 IDriver。
-
任何类都不应该从具体类派生
开发中经常从具体类派生,因为就是比某个具体类稍微多了点功能。书中也说不必苛求。
-
尽量不要覆写抽象基类的方法
如果基类是抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生一定的影响。
大概是这个意思,比如 ICar 是个抽象类,有一个默认实现的刹车,所有车都一样,立即刹车。子类不要覆盖,因为 IDriver 依赖 ICar,想要的就是立即刹车,但是重写了一个无视刹车指令,那就不是 IDriver 想要的,产生了非预期的情况,一般人不敢开这车。
当然实际中,抽象方法只是一个默认情况,就是要子类去具体实现,也还是是要看具体情况。
-
结合里氏替换原则使用
就是依赖关系发生在抽象类上,这样定义父类的地方能用具体的子类对象去使用。