依赖倒置原则
概述
定义
高层模块(即客户端/调用者)不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
简而言之:面向接口编程
问题由来
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
举个反例
public static void main(String[] args){
LiMing liMing = new LiMing();
liMing.gotoSchool(new Foot());
}
static class Foot{
public String getWay(){
return "走路";
}
}
static class LiMing{
public void gotoSchool(Foot foot){
System.out.println("李明" + foot.getWay()+ "上学");
}
}
输出
李明走路上学
虽然代码正常运转毫无问题,但是却显得很鸡肋。假设李明搬家了变远了,李明要坐公交上学怎么办?没有公交只能坐地铁怎么办?
正确的例子
interface Way{
String getWay();
}
static class Foot implements Way{
@Override
public String getWay(){
return "走路";
}
}
static class Bus implements Way{
@Override
public String getWay() {
return "坐公交";
}
}
static class Metro implements Way{
@Override
public String getWay() {
return "坐地铁";
}
}
static class LiMing{
public void gotoSchool(Way way){
System.out.println("李明" + way.getWay()+ "上学");
}
}
public static void main(String[] args){
LiMing liMing = new LiMing();
System.out.println("离学校很近");
liMing.gotoSchool(new Foot());
System.out.println("搬家了,离学校很远");
liMing.gotoSchool(new Bus());
System.out.println("没有公交车");
liMing.gotoSchool(new Metro());
}
输出
离学校很近
李明走路上学
搬家了,离学校很远
李明坐公交上学
没有公交车
李明坐地铁上学
总结
根据例子,回顾定义
高层模块(即客户端/调用者)不应该依赖低层模块,二者都应该依赖其抽象;
李明(高层模块)不依赖于具体的交通方式(底层模块),而是依赖于接口way(抽象)
抽象不应该依赖细节;
李明依赖于way,不关心具体的way怎么实现(不关心是走路还是公交或者地铁)
细节应该依赖抽象。
走路/公交/地铁这种交通方式都必须实现way接口才能被调用
优点
类之间松耦合的设计,面向接口编程依赖抽象而不依赖细节,所以在修改某个类的代码时,不会牵涉到其他类的修改,显著降低系统风险,提高系统健壮性。