1. 依赖倒置原则的定义
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念: 相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的框架比以细节的框架要稳定的多。在JAVA、C#中,抽象指的是接口或者抽象类,细节就是具体的实现类。
- 使用接口或抽象类的目的是指定好的规范,而不涉及任何的具体操作,把展现细节的任务交给他们的实现类去完成。
总之,高层模块,低层模块,细节都应该依赖抽象
依赖倒置具体表现为:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
- 接口或抽象类不依赖实现类
- 实现类依赖接口或抽象类
2. 为什么是倒转?
一般来说,我们经常为了图方便,把具体类依赖于具体类,也就是所谓的高层模块依赖于低层模块,但是这样是不利于扩展的,违反了设计原则,此时:
而现在根据依赖倒置原则,我们需要将高层模块和低层模块都依赖于接口(或者抽象类)来实现
这里发生了倒置,本来是高层模块依赖于低层模块,低层模块被高层模块依赖,现在是低层模块主动依赖于接口,所以发生了倒置,所以这个原则也称为依赖倒置原则
3. 举例
3.1 第一个例子
现在要完成一个Person类接收消息的需求;
方式1:
public class Inversion01 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
// 完成Person接收消息的功能
class Email {
public String getInfo() {
return "电子邮件信息: HELLO WORLD";
}
}
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
输出结果:电子邮件信息: HELLO WORLD
这里面Email就是低层模块,而Person是高层模块,高层模块依赖于低层模块,不符合依赖倒置原则,Person类智能接受Email类而不可以接受其他类,要改的话就要每次改代码
该方式很简单的实现了需求,但是有一些问题,我们现在接收的是邮件,如果将来需求变更需要再加上一个可以接收短信的功能,那么我们就需要新增类。同时,Person类也要增加相应的接收方法。
改进:
解决思路,引入一个抽象接口Reveiver,表示接收者,这样Person类与接口发生依赖。因为Email, 短信都属于接收范围,它们各自实现Receiver接口就行了,这样我们就符合依赖倒转原则。
public class Inversion02 {
public static void main(String[] args) {
Person02 person02 = new Person02();
person02.receive(new Email02());
person02.receive(new Message());
}
}
//先定义一个接口
interface Receiver {
String getInfo();
}
class Email02 implements Receiver {
public String getInfo() {
return "电子邮件信息: HELLO WORLD";
}
}
//增加短信
class Message implements Receiver {
public String getInfo() {
return "短信信息: HELLO WORLD";
}
}
class Person02 {
// 这里我们是对接口的依赖,稳定性较好
public void receive(Receiver receiver) {
System.out.println(receiver.getInfo());
}
}
输出结果:电子邮件信息: HELLO WORLD,短信信息: HELLO WORLD
此时,高层模块(Person)和低层模块(Email和Message)都依赖于接口(Receive),符合依赖倒置原则
3.2 第二个例子
这个例子里面有个图画错了,IReader和IRead的指向反了,其他没问题
4. 依赖的三种方法
4.1 构造函数传递依赖对象
在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入:
构造函数注入:
//小明类
public class XiaoMing implements IReader{
private IRead read;
//构造函数注入
public XiaoMing(IRead read){
this.read = read;
}
//阅读
public void read(){
read.read();
}
}
4.2 Setter方法传递依赖对象
在类中通过Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入:
//小明类
public class XiaoMing implements IReader{
private IRead read;
//Setter依赖注入
public setRead(IRead read){
this.read = read;
}
//阅读
public void read(){
read.read();
}
}
4.3 接口声明依赖
在接口的方法中声明依赖对象,该方法也叫做接口注入。
public interface IReader{
//阅读
public void read(IRead read){
read.read();
}
}
//IReader接口依赖于IRead 接口
5. 依赖倒置原则的经验
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。我们在项目中使用这个原则要遵循下面的规则:
- 每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备
- 变量的表面类型尽量是接口或者抽象类
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。
结合里氏替换原则使用
里氏替换原则:父类出现的地方子类就能出现。结合本章我们得出了一个通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系。抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要方法,在项目中,大家只要记住是”面向接口编程”就基本上是抓住了依赖倒置原则的核心了。