依赖倒转原则是什么
依赖倒转原则要求客户端依赖于抽象耦合。
依赖倒转原则的表述是:抽象不应当依赖于细节;细节应当依赖于抽象。另一种表述是:要针对接口编程,不要针对实现编程。
针对接口编程的意思就是说,应当使用 Java 接口和抽象 Java 类型进行变量的类型声明、参量的类型声明、方法的返还类型声明,以及数据类型的转换等。
不要针对实现编程的意思就是说,不应当使用具体 Java 类进行变量的类型声明、参量的类型声明、方法的返还类型声明,以及数据类型的转换等。
要保证做到这一点,一个具体 Java 类应当只实现 Java 接口和抽象 Java 类中声明过的方法,而不应当给出多余的方法。
倒转依赖关系强调一个系统内的实体之间关系的灵活性。基本上,如果设计师希望遵守“开-闭”原则,那么倒转依赖原则便是达到要求的途径。
变量的静态类型和真实类型
变量被声明时的类型叫做变量的静态类型(Static Type),有些作者把静态类型叫做明显类型(Apparent Type),变量所引用的对象的真实类型叫做变量的实际类型(Actual Type)。
List employees = new Vector();
显然,上面的代码中,employees 变量的静态类型是 List,而它的实际类型是 Vector。
引用对象的抽象类型
在很多情况下,一个 Java 程序需要引用一个对象。这个时候,如果这个对象有一个抽象类型的话,应当使用这个抽象类型作为变量的静态类型。这就是针对接口编程的含义。
不恰当的声明语句:
Vector employees = new Vector();
恰当的声明语句:
List employees = new Vector();
这两者的区别就是,前者使用一个具体类作为变量的类型,后者使用一个抽象类型(List 是一个 Java 接口)作为类型。后者的好处就在于将 Vector 类型转换为 ArrayList 时,需要改动的很少:
List employees = new ArrayList();
这样一来,程序具有更好的灵活性,因为除去调用构造子的一行语句之外,程序的其余部分根本察觉不到有什么变化。
只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明、方法返还类型声明、属性变量的类型声明等。
对象的创建
一般而言,在创建一个对象时,Java 语言要求使用 new 关键词以及这个类本身。而一旦这个对象已经被创建出来,那么就可以灵活地使用这个对象的抽象类型来引用它。因此,Java 语言中创建一个对象的过程是违背“开-闭”原则以及依赖倒转原则的。虽然这个类被创建出来以后,可以通过多态性使得客户端依赖于其抽象类型。
正是由于这个原因,设计模式给出了多个创建模式,特别是几个工厂模式,用于解决对象创建过程中的依赖倒转问题。
如何做到依赖倒转原则
以抽象方法耦合是依赖倒转原则的关键。由于一个抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
在抽象层次上的耦合虽然有灵活性,但也带来了额外的复杂性。在某些情况下,如果一个具体类发生变化的可能性非常小,那么抽象耦合能发挥的好处便十分有限,这时使用具体耦合反而会更好。
依赖倒转原则是 OO 设计的核心原则,设计模式的研究和应用是以依赖倒转原则为指导原则的。
UML类型

示例代码
package principle;
/**
* 依赖倒转
*
* @author asyyr
*/
public class DependecyInversion {
public static void main(String[] args) {
// 1、接口传递
/*Person person = new Person();
person.receive(new Email());
person.receive(new WeiXin());*/
// 2、构造方法传递
/*Person person = new Person(new Email());
person.receive();
person = new Person(new WeiXin());
person.receive();*/
// 3、setter方法传递,注意setter赋值步骤容易漏掉而导致空指针异常
Person person = new Person();
person.setReceiver(new Email());
person.receive();
person.setReceiver(new WeiXin());
person.receive();
}
}
// 1、接口传递 start
/*class Person {
public void receive(IReceiver receiver) {
System.out.println(receiver.getInfo());
}
}
interface IReceiver {
String getInfo();
}
class Email implements IReceiver {
public String getInfo() {
return "Email : Hello World";
}
}
class WeiXin implements IReceiver {
public String getInfo() {
return "WeiXin : Hello World";
}
}*/
// 1、接口传递 end
// 2、构造方法传递 start
/*class Person {
private IReceiver receiver;
public Person() {
}
public Person(IReceiver receiver) {
this.receiver = receiver;
}
public void receive() {
System.out.println(this.receiver.getInfo());
}
}
interface IReceiver {
String getInfo();
}
class Email implements IReceiver {
public String getInfo() {
return "Email : Hello World";
}
}
class WeiXin implements IReceiver {
public String getInfo() {
return "WeiXin : Hello World";
}
}*/
// 2、构造方法传递 end
// 3、setter方法传递 start
class Person {
private IReceiver receiver;
public Person() {
}
public void setReceiver(IReceiver receiver) {
this.receiver = receiver;
}
public void receive() {
System.out.println(this.receiver.getInfo());
}
}
interface IReceiver {
String getInfo();
}
class Email implements IReceiver {
public String getInfo() {
return "Email : Hello World";
}
}
class WeiXin implements IReceiver {
public String getInfo() {
return "WeiXin : Hello World";
}
}
// 3、setter方法传递 end
在设计模式的体现
工厂方法模式
应当使消费一个对象的客户端只依赖于对象的抽象类型,而不是它的具体类型。但是,Java 语言要求将一个(具体)类实例化的时候,必须调用这个具体类的构造方法,所以 Java 语言给出的类的实例化方法无法做到只依赖于抽象类型。
但是设计模式给出了解决这个问题的可行方案,其中最重要的方案就是工厂模式。工厂方法模式是几个工厂模式中最为典型的一个,下图所示就是工厂方法模式的简略类图。
工厂模式将创建一个类的实例的过程封装起来,消费这个实例的客户端仅仅得到实例化的结果,以及这个实例的抽象类型。当然,任何方法都无法回避 Java 语言所要求的 new 关键字和直接调用具体类的构造方法的做法。简单工厂模式将这个违反“开-闭”原则以及依赖倒转原则的做法封装到了一个类里面,而工厂方法模式将这个违反原则的做法推迟到了具体工厂角色中,如下图所示:
通过适当的封装,工厂模式可以净化大部分的结构,而将违反原则的做法孤立到易于控制的地方。
模版方法模式
模方法模式是依赖倒转原则的具体体现,模版方法模式的简略类图如左下图:
在模版方法模式里面,有一个抽象类将重要的宏观逻辑以具体方法以及具体构造方法的形式实现,然后声明一些抽象方法来迫使子类实现剩余的具体细节上的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。模版方法模式支持依赖倒转的原则,如右下图所示:
具体子类不能影响抽象类的宏观逻辑,而抽象逻辑的改变则会导致细节逻辑的改变。
迭代子模式
迭代子模式用一个工厂方法向客户端提供一个聚集的内部迭代功能,客户端得到的是一个 Iteraotr 抽象类型,并不知道迭代子的具体实现以及聚集对象的内部结构。迭代子模式的简略类图如下图所示:
这样一来,聚集的内部结构的改变就不会波及到客户端,从而实现了对抽象接口的依赖,如下图所示:
依赖倒转原则的优缺点
依赖倒转原则虽然很强大,但却是最不容易实现的。因为依赖关系倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。对不熟悉面向对象技术的工程师来说,维护这样的系统需要较好的面对对象的设计的知识。
此外,依赖倒转原则假定所有的具体类都是会变化的,这也不总是正确的。有一些具体类可能会相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类型,而不必为此发明一个抽象类型。