(Dependence Inversion Principle)定义: 高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。//“抽象”指“接口或抽象类”,“细节”指“实现类”
在语言中表现:
模块间的依赖是通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
”A依赖B“指 什么?依赖传递的3种方式?
问题(不好的情况):
概述: 再 “依赖” 关系中,程序要依赖于抽象接口,不要依赖于具体实现。这样降低了客户与实现模块间的耦合。
谁依赖谁要抽象的合理。
采用依赖倒置原则的好处? 可以减少类间的耦合性,提高系统的稳定性,减少并行开放引起的风险,提高代码的可读性和可维护性
3 举例说明: 司机驾驶奔驰如3-1图:
3-1 司机源码
public class Driver{
public void drive(Benz benz){
benz.run();
}
}
3-2 奔驰车源代码
public class Bez{
public void run(){
System.out.println("奔驰开始运行...")
}
}
public class Client{
public static void main(Sting[] args){
Driver zhangsan = new Driver();
Benz benz = new Benz();
zhangsan.drive(benz);
}
}
以上代码也算是完成了任务或者基本需求,现在又来新需求了,张三不仅要开奔驰车,还要开宝马车,又该怎么办呢?
3-4 宝马车源代码
public class BMW{
public void run(){
System.out.println("宝马车开始运行....");
}
}
问题来了,宝马车也产生了,张三没法开?因为张三没有开宝马车的方法!(除非要改driver类的方法),一个有驾照的司机竟然只能开奔驰车,而不能开宝马车(要开宝马车改动driver类代码),这也不合理,在现实世界也不合理。 问题关键点在什么地方?就是司机类和奔驰车类之间是一个紧耦合关系,这里新增加一个车类就要修改司机类,这是不稳定性!被依赖者的变更或增加(新增宝马车类)竟然要依赖者(司机类)来承担修改的成本,不好呀!!!
这样的代码类关系还会引起并行开发的风险; 什么是并行开发风险? 并行开发最大的风险就是风险扩散,本来只是一段程序的错误或异常,逐步波及一个功能,一个模块,甚至最后毁坏整个项目! 为什么风险可能如此大呢? 一个团队,20人开发,个人负责不同的功能模块,甲负责汽车类,乙负责司机类, 在甲没有完成的情况,乙是不能完全编写代码的,缺少汽车类,编译器根本不会让你通过,更不要说单元测试了! 在上面那种不使用依赖倒置原则环境中,所有的开发工作都是单线程的,甲做完,乙再做,然后是丙...., 在小项目中是可以的一个人完成所有的代码开发工作,但是在现在的大中型项目中已经是完全不能胜任了;要协作就要并行开发,要并行开发就要解决模块之间的项目依赖关系,依赖倒置原则就可以做到!!!
引入了依赖倒置原则后的类如图3-2
建立两个接口:IDriver 和 ICar
3-5 司机接口
public interface IDriver{
public void drive(ICar car);
}
public class Driver implements IDriver{
public void drive(ICar car){
car.run();
}
}
3-7 汽车类及两个实现类
public interface ICar{
public void run();
}
public class Benz implements ICar{
public void run(){
System.out.println("奔驰骑车开始运行....");
}
}
public class BMW implements ICar{
public void run(){
System.out.println("宝马骑车开始运行....");
}
}
3-8 业务场景代码
public class Client{
public static void main(String[] args){
IDriver zhangsan = new Driver();
ICar benz = new Benz();
zhangsan.drive(benz);
}
}
Client 属于高层业务逻辑,它对底层模块的依赖(关联)都建立在抽象上,zhangsan 的显示类型是IDriver, benz的显示类型是ICar, 也许你要问,在这个高层模块中也调用了底层模块,比如 new Driver() 和 new Benz()等,怎么解释? 确实如此,zhangsan的显示类型是IDriver,是一个接口,是抽象的,非实体化的,在后续的操作中zhangsan 都是以IDriver类型进行操作,屏蔽了细节对抽象的影响。当然,张三如果要开宝马,很容易,只要修改业务场景类就可以了。如下:
3-9 张三驾驶宝马车过程
public class Client {
public static void main(String[] args) {
IDriver zhangSan = new Driver();
ICar bmw = new BMW();
//张三开奔驰车
zhangSan.drive(bmw);
}
}
点评: 在新增加底层模块时,只修改了业务场景类,也就是高层模块,对其他模块如Driver类不需要做任何修改,把”变更“的风险降到最低。
依赖倒置对并行开发的影响。两个类有依赖关系,只要指定两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立的运行,而TDD( Test-Driven Development,测试驱动开发) 开发模式就是依赖倒置原则的最高级应用。甲程序员负责IDriver 的开发,乙程序员负责ICar的开发,两个人只要制定好了接口就可以独立开发了,甲开发的快,完成了IDriver以及相关实现类Driver的开发工作,而乙开发滞后,那么甲是否可以进行单元测试(Unit Test)答案是可以的,我们引入一个JMock工具。todo.....
抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不逃脱契约的范畴,确保约束双方按照既定的契约(抽象)共同发展。
依赖的三种写法,或者依赖传递的三种方式?
依赖是可以传递的,A对象依赖B对象,B依赖C,C依赖D....生生不息;记住只要做到依赖 抽象,即使是多层的依赖传递也无所谓!
1. 构造函数传递依赖对象
public interface IDriver{
public void drive();
}
public class Driver implements IDriver{
private ICar car;
public Driver(ICar _car){
this.car = _car;
}
public void drive(){
this.car.run();
}
}
2. setter方法传递依赖对象
public interface IDriver{
public void setCar(ICar car);
public void drive();
}
public class Driver implements IDriver{
private ICar car;
public void setCar(ICar car){
this.car = car;
}
public void drive(){
this.car.run();
}
}
3.4 编码最佳实践
依赖倒置的本质原则就是 :通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。
1. 每个类尽量都要有接口或抽象类,或者抽象类和接口两者都具备。
2. 变量的显示类型 尽量是接口或者抽象类。
3. 任何类都不应该从具体类派生。 这样不是绝对的如,在项目开发阶段要遵从此原则。要是做维护老项目,基本就是做扩展开发,通过继承关系,覆写一个方法就可以修改一个bug,何必要去继承最高的基类内容
4. 尽量不要覆写基类的方法
5. 结合里氏替换原则。 父类出现的地方子类就能出现
”倒置“怎么理解 ? 依赖正置 ,我要开奔驰就依赖奔驰,面向实现编程,估计这也是90年代主要思想,后来这种方式有很大问题 所以要”倒置“即依赖抽象。
现实世界中,有些必须依赖细节 如:法律,”杀人偿命” 这个算依赖抽象,实际上,要具体情况(依赖细节)才能判决是否要偿命
参考博客http://www.cnblogs.com/cbf4life/archive/2009/12/15/1624435.html