一、定义
1、模块间要依赖抽象,不要通过具体的实现类。依赖关系通过接口(抽象)进行编程,这就降低客户与实现模块间的耦
合。(接口或抽象类不依赖于实现类,实现类依赖接口或抽象类 面向接口编程OOD) 包含三层含义:
(1)高层模块不应依赖于低层模块,两者都应该依赖其抽象
(2)抽象不应该依赖细节 (3)细节应该依赖于抽象
2、高层模块和底层模块的概念
(1)低层模块:每个逻辑的实现都是原子逻辑组成,不可分割的原子逻辑就是低层模块。一般和具体的实现相关。
(2)高层模块:原子模块再组装就是高层模块,一般和业务逻辑相关。如客户端。
Java语言中,抽象就是指的是接口或抽象类,两者都不能直接被实例化。
细节就是实现类,实现接口或继承抽象类而产生的类就是细节,特点是直接可以被实例化。
3、何为"倒置"
(1)、依赖正置:类间的依赖是实实在在的实现类间的依赖,面向实现编程,符合人类正常的思维。
(2)、依赖倒置:编程就是对现实世界事务进行抽象,然后根据系统设计产生对抽象的依赖,代替事物间的依赖,称
为"倒置"。
二、依赖实现编程存在的问题及改进
1、Driver只能开奔驰车!
Client是场景类
实验:依赖于实现类,导致司机只能开奔驰车
//面向对象设计原则:DIP依赖倒置原则 //司机只能开奔驰车——依赖具体实现 #include <stdio.h> //奔驰车类 class Benz { public: void run() { printf("Benz Runing...\n"); } }; //司机类 class Driver { public: //司机类不是依赖于抽象,而是依赖具体的汽车Benz, //导致司机只能开奔驰,不能开其它车的尴尬! void drive(Benz& benz) { benz.run(); } }; int main() { Driver zhangSan; Benz benz; //张三开奔驰车 zhangSan.drive(benz); //参数为Benz类型,张三只会开奔驰! return 0; }有车、有司机,在场景类产生相应的对象。到目前为止,这个司机开奔驰车的项目没有问题。但是业务需求变更永无
休止,技术前进就永无止境,在发生变更的时候才发觉我们设计的程序是否是松耦合。
现在呢,张三司机又想开宝马车,怎么实现呢?设计出现问题:司机类和奔驰车类之间是紧耦合的关系,导致的结果
就是系统的可维护性降低,可读性降低,稳定性低。
2、依赖倒置隆重登场
(1)、好处:减少类间耦合,提高系统的稳定性,提高代码的可维护性和可读性,降低并行开发引起的风险
(2)、通过IDriver和ICar两个接口来实现类间的耦合,引入依赖倒置原则。
接口只是一个抽象化的概念,是对一类事物抽象的描述,具体的代码由相应的实现类来实现。
(3)汽车提供run方法。司机的只能就是驾驶汽车,必须实现Driver方法。新增汽车类只需要实现ICar接口。
(4)ICar接口实现抽象之间的依赖关系,Driver实现类也传入ICar接口,到底是那个Car,需要在高层模块声明。
(5)业务场景类中,抽象不应该依赖细节,也就是抽象(接口)不依赖于实现类(细节),高层模块应用的都是抽象。
编程:司机可以开各种车
//面向对象设计原则:DIP依赖倒置原则 //司机可开任何汽车——依赖抽象/接口 #include <stdio.h> //汽车接口 class ICar { public: virtual void run() = 0; }; //奔驰车类 class Benz : public ICar { public: void run(){printf("Benz runing...\n");} }; //宝马车类 class BWM : public ICar { public: void run(){printf("BWM runing...\n");} }; //司机接口 class IDriver { public: //是司机应该会驾驶汽车 virtual void drive(ICar& car) = 0; //依赖接口 }; //司机类 class Driver : public IDriver { public: void drive(ICar& car) //实现接口 { car.run(); } }; int main() { Driver zhangSan; //引用(对象) 指针(new) Benz benz; BWM bwm; //张三开奔驰车 zhangSan.drive(benz); //张三开宝马 zhangSan.drive(bwm); return 0; }
Client属于高层业务逻辑,对低层的依赖都建立在抽象上,zhangsan都是以IDriver类型进行操作,屏蔽了细节对抽
象的影响。java中,一个变量可以有两种类型:表面类型和实际类型,表面类型是定义的时候赋予的类型,实际类型
是对象的类型。如zhangsan的表面类型是IDriver,实际类型是Driver。
三、依赖的3种写法
1、构造函数传递依赖对象
2、setter方法传递依赖对象
3、通过接口声明依赖对象(接口注入)
四、最佳实践
依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或者模块的实现彼此独立,不相互影响,实现模块间的松
耦合。
1、每个类尽量都有接口或者抽象类,或者两者都有,有了抽象才能依赖倒置
2、声明变量时尽量用接口或抽象类,实例化再使用具体的类。变量的表面类型尽量是接口或者抽象类
3、任何类都不应该从具体的类派生(或者继承具体的类不超过两层)
4、尽量不要覆写基类已经实现的方法。如果基类是抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依
赖的是抽象,覆写抽象方法,对依赖的稳定性产生影响。
5、结合里氏替换原则对子类进行设计。
接口负责定义public属性和方法,并且声明与其他对象的依赖关系。
抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
"面向接口编程"基本上是依赖倒置原则的核心。