三、 依赖倒转原则
1. 概念
高层模块不应该依赖低层模块,应该依赖其抽象;抽象不应该依赖细节(具体的实现类或者子类),细节应该依赖抽象。简单的说就是要求对抽象进行编程不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
我第一次看到这个概念的时候,一脸懵逼。下面让我们通过几幅图来看看,加深理解。
先是:高层模块不应该依赖低层模块 这句话,什么是高层模块,什么是低层模块。
这里拓展下UML类图的知识点,依赖关系是对象间耦合度最弱的一种关联方式,在代码中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。如下图,学校里有老师上课。
依赖抽象:怎么理解呢?
Teacher类不是一个接口,也不是一个抽象类。而我们的School还以来了Teacher,所以我们需要将Teacher类进行向上抽取。抽取成 抽象类 或者 接口,此时我们的School再去依赖这个抽象类或者接口。这就是依赖抽象的概念
知识点补充:继承和实现关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在 UML 类图中,继承关系用带空心三角箭头的实线来表示,箭头从子类指向父类。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从子类指向父类。
下面这幅图就是依赖抽象
依赖倒转原则是开闭原则的一种实现,大家可以想一想,开闭原则的内容:对扩展开放,对修改关闭。而我们的依赖倒转原则主要就是四个字:依赖抽象。
我们在日常开发代码中,写的方法,里面是不是经常会看到参数是接口的形式,比如我们要接收一个collection集合,但是在实现的时候是不是可以把list传进去。也可以把set传进去。
找到感觉了吗?
如果某一天我们要写一个属于自己的集合xxx,我们也可以实现collection,这样不用修改之前的方法。传入我们自己的xxx集合,这满足开闭原则,没有动原代码,增加了功能。
下面举个例子,继续帮大家找感觉
【组装电脑】
现要组装一台电脑,需要配件cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常的运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿,海盗船等。
首先我们先写一个计算机,里面用的是希捷硬盘,Intel的cpu,金士顿的内存条去组装一台计算机。下面是他的类图。
代码:
/**
* 希捷硬盘
*/
public class XiJieHardDisk {
/**
* 存储数据
*
* @param data 存储的数据
*/
public void save(String data) {
System.out.println("使用希捷硬盘存储数据为:" + data);
}
public String get() {
System.out.println("使用希捷硬盘获取数据");
return "数据";
}
}
/**
* 金士顿内存条
*/
public class KingstonMemory {
public void save(String data) {
System.out.println("使用金士顿内存条存储数据为:" + data);
}
}
/**
* Intel的处理器
*/
public class IntelCpu {
public void calculate() {
System.out.println("使用Intel处理器计算");
}
}
@Data
public class Computer {
private XiJieHardDisk xiJieHardDisk;
private IntelCpu intelCpu;
private KingstonMemory kingstonMemory;
public void run() {
System.out.println("电脑运行");
String data = xiJieHardDisk.get();
System.out.println("从硬盘中获取的数据是:" + data);
intelCpu.calculate();
kingstonMemory.save("data");
}
}
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.setIntelCpu(new IntelCpu());
computer.setKingstonMemory(new KingstonMemory());
computer.setXiJieHardDisk(new XiJieHardDisk());
computer.run();
}
}
我们可以看得出来,我们定义的这个电脑就成功的运行起来了。但是这样写,可扩展性就很低,而且不满足开闭原则,如果我的电脑想换一种cpu呢,是不是就得修改原有的代码?
这时候,想起来四个大字:依赖抽象
我们要根据依赖倒转原则来重写,下面是新的结构类图
更新后的代码:
/**
* 硬盘接口
*/
public interface HardDisk {
String get();
void save(String data);
}
/**
* 希捷硬盘
*/
public class XiJieHardDisk implements HardDisk {
/**
* 存储数据
*
* @param data 存储的数据
*/
@Override
public void save(String data) {
System.out.println("使用希捷硬盘存储数据为:" + data);
}
@Override
public String get() {
System.out.println("使用希捷硬盘获取数据");
return "数据";
}
}
/**
* Cpu接口
*/
public interface Cpu {
void calculate();
}
/**
* Intel的处理器
*/
public class IntelCpu implements Cpu {
@Override
public void calculate() {
System.out.println("使用Intel处理器计算");
}
}
/**
* AMD CPU
*/
public class AmdCpu implements Cpu {
@Override
public void calculate() {
System.out.println("使用AMD处理器");
}
}
/**
* 内存条接口
*/
public interface Memory {
void save(String data);
}
/**
* 金士顿内存条
*/
public class KingstonMemory implements Memory {
@Override
public void save(String data) {
System.out.println("使用金士顿内存条存储数据为:" + data);
}
}
/**
* 电脑实体
*/
@Data
public class Computer {
private HardDisk hardDisk;
private Cpu cpu;
private Memory memory;
public void run() {
System.out.println("电脑运行");
String data = hardDisk.get();
System.out.println("从硬盘中获取的数据是:" + data);
cpu.calculate();
memory.save("data");
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
//使用Intel的Cpu,希捷的硬盘,金士顿的内存条
getComputerOne();
System.out.println("========================");
//使用AMD的Cpu,希捷的硬盘,金士顿的内存条
getComputerSecond();
System.out.println("========================");
//使用lambda表达式(匿名内部类),前提是:接口中只有一个方法
getComputerThree();
}
/**
* 第一台计算机:感受依赖倒转原则
*/
private static void getComputerOne() {
Computer computer = new Computer();
computer.setCpu(new IntelCpu());
computer.setMemory(new KingstonMemory());
computer.setHardDisk(new XiJieHardDisk());
computer.run();
}
/**
* 第二台计算机:感受依赖倒转原则
*/
private static void getComputerSecond() {
Computer computer = new Computer();
computer.setCpu(new AmdCpu());
computer.setMemory(new KingstonMemory());
computer.setHardDisk(new XiJieHardDisk());
computer.run();
}
/**
* 第三台计算机:使用匿名内部类实现接口,前提是:接口中只有一个方法
*/
private static void getComputerThree() {
Computer computer = new Computer();
computer.setCpu(() -> System.out.println("正在使用Intel的最新Ultra处理器来运行程序:"));
computer.setMemory(new KingstonMemory());
computer.setHardDisk(new XiJieHardDisk());
computer.run();
}
}
经过上述的例子,对依赖倒转原则和开闭原则理解的是不是又加深了!!!后面还有很多,我们慢慢来。