依赖倒置原则
定义
1.高层模块不应该依赖低层模块,两者都应该依赖于抽象
2.抽象不应该依赖于细节
3.细节应该依赖于抽象
解释:
1.什么是低层模块?
就是原子逻辑组成低层模块。
2.什么是高层模块?
就是原子逻辑的再组装,就变成了高层模块。
3.什么是抽象?
抽象就是接口或者抽象类。
4.什么是细节?
细节就是实现类。
那上面三句话的意思就是:
高层模块不依赖低层模块,容易理解,模块之间的依赖应该通过接口或者抽象类,而不应该通过实现类
抽象不应该依赖细节,就是说接口或者抽象类不应该依赖于实现类
细节应该依赖抽象的意思就是说,实现类应该依赖于抽象的接口或者抽象类。
如果不依赖抽象,依赖实现,我们看看会是什么结果?
public class Benz{
public void run(){
System.out.println("奔驰车在路上跑");
}
}
public class Driver{
//这里依赖的是具体实现
public void drive(Benz benz){
benz.run();
}
}
public class Client{
public static void main(String args[]){
Benz benz = new Benz();
Driver driver = new Driver();
driver.drive();
}
}
这里可以看到,奔驰车在跑,当需要扩展添加宝马车时,我们完全可以添加一台宝马车.
public class Bmw{
public void run(){
System.out.println("宝马车在路上跑");
}
}
但是drive方法依赖的是具体实现,怎么让司机驾驶宝马车呢?这样就比较麻烦,所以说依赖于具体实现不利于扩展。
再来看看,如果是依赖抽象呢?
public interface ICar {
/**
* 汽车就应该能在路上跑
*/
public void run();
}
public interface IDriver {
/**
* 司机就应该会驾驶汽车
*/
public void drive(ICar car);
}
再添加实现类。
public class Benz implements ICar {
@Override
public void run(){
System.out.println("奔驰车在路上跑");
}
}
public class Bmw implements ICar{
@Override
public void run() {
System.out.println("宝马车在路上跑");
}
}
public class Driver implements IDriver{
@Override
public void drive(ICar car) {
car.run();
}
}
public class Client {
public static void main(String[] args) {
IDriver iDriver = new Driver();
ICar car = new Bmw();
iDriver.drive(car);
}
}
由上面的代码可以看到,在drive方法里传递的是抽象,而不是实现,那么当需要宝马车的时候,只需要修改高层模块,以及扩展所需要的宝马车,其他实现根本不需要改变。这样就可以减少耦合性,提高扩展性了。
依赖的传递
常见的又三种方式传递依赖,上面的代码中已经有了接口传递依赖的例子:
//接口传递依赖,应该依赖抽象
public void drive(ICar icar);
还有两种方式传递依赖:
1.构造函数传递依赖
public class UserServiceImpl implements UserService{
private UserDao userDao;
public UserServiceImpl(UserDao userDao){
this.userDao = userDao;
}
}
这就是构造函数传递依赖。
setter方法传递依赖
public class UserController{
private UserService userService;
//setter方法传递依赖
public void setUserService(UserService userService){
this.userService = userService;
}
}
总结:
依赖倒置原则是最难以实现的原则,但是它又是实现开闭原则的重要途径。在开发的时候,遵守原则固然是一件好事,但是也要根据实际情况决定。
依赖倒置原则的基本要求:
1.每个类都应该尽量有接口或者抽象类,甚至接口或抽象类都具备
2.变量的表面类型应该是接口或抽象类,而不是实现类
3.任何类都不应该由具体类派生。也就暗含了第一条,最好是有接口或抽象类,但是实际中可能难以做到,实在做不到,应该保证尽量少继承实现类。
4.尽量不要重写基类的方法:如果基类是一个抽象类,并且这个方法已经实现了,子类尽量不要去重写这个基类的抽象方法。
5.结合里氏替换原则使用:
①接口负责定义public属性和方法,并声明与其他对象的依赖关系
②抽象类负责公共构造部分的实现
③实现类准确的实现业务逻辑