一. 依赖倒置原则的定义
1.核心思想
依赖倒置原则(Dependence Inversion Principle,DIP)
*1.高层模块 不应该依赖 低层模块, 二者都应该依赖其抽象;
*2.抽象 不应该依赖 细节, 细节 应该依赖 抽象; 接口和抽象类价值在于设计;
*3.依赖倒转(倒置)的中心思想是面向接口编程,要去(面向)实现接口,而不是继承或调用实现类;
*4.依赖倒转原则的设计理念:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的框架比以细节为基础
的架构要稳定的多,在java中,抽象指接口或抽象类(抽象战略方法),细节就是具体的实现类(多变战术实现);
使用接口或抽象类的目的是制定好规范(设计),而不涉及任何具体操作,把处理细节的任务交给子实现类去完成;
2.注意事项和细节
1)注意事项
*1.低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
*2.变量的声明类型尽量是抽象类或接口,这样变量的引用和实际对象间,就存在一个缓冲层,利于程序的扩展和优化;
*3.继承时遵循里氏替换原则;
2)注意细节
*1.高层模块不应该依赖低层模块,两者都应该依赖于抽象;
*2.抽象不应该依赖于细节(具体实现类);
*3.细节(具体实现类)应该依赖于抽象(实现抽象);
二.依赖倒置原则的作用
1. 依赖倒置原则可以降低类间的耦合性。
2. 依赖倒置原则可以提高系统的稳定性。
3. 依赖倒置原则可以减少并行开发引起的风险。
4. 依赖倒置原则可以提高代码的可读性和可维护性。
三.依赖倒置实现方式
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
1. 每个类尽量提供接口或抽象类,或者两者都具备;
2. 变量的声明类型尽量是接口或是抽象类;
3. 任何类都不应该从具体类派生;
4. 使用继承时尽量遵循里氏替换原则;
四.依赖关系传递的三种方式
*1.通过构造函数传递依赖对象:通过构造方法传递接口实现类 (例如构造函数的形参是抽象类或接口 Entry(Interface iView);)
*2.通过setter方法传递依赖对象:通过setter方法传递接口实现类(setListener(Interface iView){this.iView=iView;} )
*3.接口声明实现依赖对象:直接通过方法传递接口实现类
示例 IOpenAndClose 依赖 ITV
//TV接口
interface ITV{ fun play()}
//实现类
class ChangHong : ITV {
override fun play() {
println("长虹电视机打开了")
}
}
/**
* 依赖关系传递的三种方式
* 1.基于接口传递 : 接口回调传值
* 2.基于构造方法传递
* 3.setter方式传递
*/
//方式1:通过接口传递实现依赖
fun func1(){
val changHong:ChangHong= ChangHong()
val openAndClose:OpenAndClose= OpenAndClose()
openAndClose.open(changHong)
}
interface IOpenAndClose{
fun open(tv:ITV)//抽象方法,接收接口
}
//实现类,通过传递接口实现类调用接口方法
class OpenAndClose : IOpenAndClose{
override fun open(tv: ITV) { tv.play() }
}
//方式2 通过构造方法依赖传递
fun func1(){
val changHong:ChangHong= ChangHong()
val openAndClose: OpenAndClose = OpenAndClose(changHong)
openAndClose.open()
}
interface IOpenAndClose{
fun open()//抽象方法
}
//通过实现类的构造器传递接口 依赖
class OpenAndClose(tv:ITV) : IOpenAndClose{
private val mTv: ITV = tv
override fun open() {
mTv.play()
}
}
//方式3:通过Setter方法 依赖
fun func3(){
val changHong: ChangHong = ChangHong()
val openAndClose: OpenAndClose = OpenAndClose()
openAndClose.setTv(changHong)
openAndClose.open()
}
interface IOpenAndClose {
fun open()//抽象方法
fun setTv(tv: ITV)
}
//通过实现类调用setter方法传递依赖
class OpenAndClose : IOpenAndClose {
private var mTv: ITV? = null
override fun open() {
mTv?.play()
}
override fun setTv(tv: ITV) {
mTv = tv
}
}
五.实例
让细节的具体实现类去依赖(使用)抽象类或接口,而不是让接口或抽象类去依赖细节的具体实现;依赖倒置原则在“接收消息功能程序”中的应用:
1.类图
2.代码示例
object DependenceInversion {
@JvmStatic
fun main(args: Array<String>) {
val person = Person()
person.receive(Email())
person.receive(WeChat())
}
}
/**
* 需求: 完成Person接收消息的功能
* 方式1分析
* 1.简单,比较容易想到:通过建立具体的消息类如: Email WeChat Sms来调去他们各自的getInfo方法
* 2.问题:这种实现会因需求而增加新类,同时,person也要新增相应的接收方法
* 3.解决思路:引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver建立依赖
* 4.因为Email,WeChat 等都属于IReceiver的接收范围,他们各自实现了IReceiver接口,这样我们就符合依赖倒置原则了
* @author xuezhihui
* @date 2020/11/23 20:03
*/
open class Person {
//这里Person是对接口的依赖, 传参是接口的实现类
fun receive(receiver: IReceiver?) {
println(receiver?.getInfo())
}
}
//信息接收器
interface IReceiver {
fun getInfo(): String?
}
//接收E-mail信息
class Email :IReceiver {
override fun getInfo(): String? {
return "Email接收的信息"
}
}
//接收微信信息
class WeChat :IReceiver {
override fun getInfo(): String? {
return "WeChat接收的信息"
}
}
程序的运行结果如下:
Email接收的信息
WeChat接收的信息