一、什么是依赖倒置原则
一种表述:
抽象不应当依赖于细节;细节应当依赖于抽象。
另一种表述:
要针对接口编程,不要针对实现编程。
针对接口编程的意思就是说,应当使用Java接口和抽象Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
不要针对实现编程的意思就是说,不应当使用具体Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
二、为什么要倒置
传统的过程性系统的设计方法倾向于使高层次的模块依赖于低层次的模块,抽象层次依赖于具体层次。倒置原则就是要把这个错误的依赖关系倒转过来。
抽象层次包含的是应用程序的商务逻辑和宏观的、对整个系统来说重要的战略性决定,是必然性的体现;而具体层次则含有一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。具体层次的代码经常会有变动,不能避免错误。抽象层次依赖于具体层次,使许多具体层次的细节的算法变化立即影响到抽象层次的宏观的商务逻辑,倒置微观决定宏观,战术决定战略,偶然决定必然,这不是很荒唐吗。
三、再看工厂方法模式
按照依赖倒置原则,客户端应该依赖于对象的抽象类型而不是它的具体类型,但是在Java中使用new创建一个具体对象实例时必须调用具体类的构造方法,所以Java语言给出的类的实例无法做到只依赖于抽象类型。但是我们可以做到部分依赖,因为Java中有多态的特性。例如A是一个抽象类,B是继承自A的一个具体子类,我们可以写如下的代码:
B b = new B();
这是一个完全依赖具体的不好的写法,我们可以利用多态性写成下面这样:
A a = new B();
这样就变成了部分依赖,前边的A是依赖于抽象的,这样当B换成其他的继承自A的子类的时候,其他的程序是不会受到影响的。即使是这样还是没有做到完全依赖于抽象,在设计模式中前边讲过的工厂方法模式就解决了这个问题。
工厂方法模式将创建一个对象的工程封装起来,客户端仅仅得到这个实例化的结果,以及这个实例的抽象类型,当然工厂方法也不能避免要用new创建对象的限制,但是工厂模式将这个违反规则的做法推迟到了具体工厂角色中。将违反规则的做法孤立于易于控制的地方。
四、实例分析
这是一个银行账户的例子,一个Account包含两个部分,AccountType和AccountStatus,这两个类都是抽象类,这两个类分别有两个具体的子类,Account在使用的是抽象类而不是子类,从而在Account的角度上是符合依赖倒置原则的。
package com.designphilsophy.dip;
/** * 账户 * @author xingjiarong * */
public class Account {
private AccountType accountType;
private AccountStatus accountStatus;
public Account(AccountType acctType) {
// write your code here
}
public void deposit(float amt) {
// write your code here
}
}
package com.designphilsophy.dip;
/** * 账户的状态是开的状态还是超支状态 * @author xingjiarong * */
public abstract class AccountStatus {
public abstract void sendCorrespondence();
}
package com.designphilsophy.dip;
/** * 账户的类型是储蓄卡还是支票 * * @author xingjiarong * */
public abstract class AccountType {
public abstract void deposit(float amt);
}
package com.designphilsophy.dip;
/** * 支票 * * @author xingjiarong * */
public class Checking extends AccountType {
public void deposit(float amt) {
// write your code here
}
}
package com.designphilsophy.dip;
/** * 储蓄卡 * * @author xingjiarong * */
public class Savings extends AccountType {
public void deposit(float amt) {
// write your code here
}
}
package com.designphilsophy.dip;
/** * 开状态 * * @author xingjiarong * */
public class Open extends AccountStatus {
public void sendCorrespondence() {
// write your code here
}
}
在这个例子中,Account类依赖于AccountType和AccountStatus,但是这两个类都是抽象类,如果我们想再添加一个新的状态超支状态的话,不会对其他的类造成影响。
package com.designphilsophy.dip;
/** * 超支状态 * @author xingjiarong * */
public class Overdrawn extends AccountStatus {
public void sendCorrespondence() {
// write your code here
}
}