依赖倒置原则的定义
依赖倒置原则(Dependence Inversion Principle,DIP)是 Object Mentor 公司总裁罗伯特·马丁(Robert C.Martin)于 1996 年在 C++ Report 上发表的文章。
依赖倒置原则的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口编程,不要面向实现编程。
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。
使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
依赖、倒置原则的作用
依赖倒置原则的主要作用如下。
- 依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则可以提高系统的稳定性。
- 依赖倒置原则可以减少并行开发引起的风险。
- 依赖倒置原则可以提高代码的可读性和可维护性。
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 使用继承时尽量遵循里氏替换原则。
在使用依赖倒置原则时需要注意如下几个问题:
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。
常用的注入方式有三种,分别是:
- 构造注入,构造注入是指通过构造函数来传入具体类的对象;
- 设值注入(Setter注入) 设值注入是指通过Setter方法来传入具体类的对象;
- 接口注入,接口注入是指通过在接口中声明的业务方法来传入具体类的对象;
这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
样例说明:
下面以“不同数据转换格式”为例介绍加深理解:
某系统要提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,比如可以转换来自数据库的数据,来自文本的数据,转换后的格式时XML ,也可以是XLS。
原始的:
数据库数据源:
package com.wry.principle.inversion.dataconversion.primeval;
/**
* 从数据库获取数据
*/
public class DatabaseSource extends DataConversion {
public String getData(){
return "从数据库获取数据";
}
}
数据转换类:
package com.wry.principle.inversion.dataconversion.primeval;
/**
* 把数据转换为XML形式
*/
public class DataConversion {
public XmlData conversionData(DatabaseSource source) {
return new XmlData(source.getData()+"\t 转换为XML 数据");
}
}
XML 数据类:
package com.wry.principle.inversion.dataconversion.primeval;
public class XmlData extends DataConversion {
private String data;
public XmlData(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
客户端使用:
package com.wry.principle.inversion.dataconversion.primeval;
public class Client {
public static void main(String[] args) {
DatabaseSource databaseSource=new DatabaseSource();
databaseSource.getData();
DataConversion dataConversion=new DataConversion();
XmlData xmlData = dataConversion.conversionData(databaseSource);
System.out.println(xmlData.getData());
}
}
现在该系统需求发生变化,可能要增加新的数据源和新的数据格式,如果按照现在的系统设计,那么将会修改客户端的源码,才能实现新需求,这就违反了开闭原则,我们将使用依赖倒转原则对系统进行改造,抽象数据源,新增数据格式父类,让新增的数据继承父类,数据转换类面向抽象类,与父类进行编程。
改造后:
抽象数据源类:
package com.wry.principle.inversion.dataconversion.remould;
public abstract class AbstractSource {
public abstract String getData();
}
数据库数据源类:
package com.wry.principle.inversion.dataconversion.remould;
public class DatabaseSource extends AbstractSource {
@Override
public String getData() {
return "从数据库获取数据";
}
}
文本文件数据源类:
package com.wry.principle.inversion.dataconversion.remould;
public class TxtSource extends AbstractSource {
@Override
public String getData() {
return "从文本文件获取数据";
}
}
数据格式父类:
package com.wry.principle.inversion.dataconversion.remould;
public class DataFormat {
private String data;
public DataFormat() {
}
public DataFormat(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
Xlst数据格式类:
package com.wry.principle.inversion.dataconversion.remould;
public class XlstData extends DataFormat {
public XlstData() {
super();
System.out.print("----------【XLST】-----------");
}
public XlstData(String data) {
super(data);
System.out.print("----------【XLST】-----------");
}
}
XML数据格式类:
package com.wry.principle.inversion.dataconversion.remould;
public class XmlData extends DataFormat {
public XmlData() {
super();
System.out.print("----------【XML】------------");
}
public XmlData(String data) {
super(data);
System.out.print("----------【XML】------------");
}
}
数据转换类:
package com.wry.principle.inversion.dataconversion.remould;
/**
* 把数据转换类
* 利用反射原理动态生成要转换的格式
*/
public class DataConversion {
public DataFormat conversionData(String className, AbstractSource source) {
DataFormat obj = null;
try {
Class c = Class.forName(className);
obj = (DataFormat) c.newInstance();
obj.setData(source.getData() + "\t------【数据转换】");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return obj;
}
}
客户端使用:
package com.wry.principle.inversion.dataconversion.remould;
public class Client {
public static void main(String[] args) {
//数据库数据源
AbstractSource dataSource = new DatabaseSource();
dataSource.getData();
DataConversion conversion = new DataConversion();
//后期可通过配置文件动态配置类名
DataFormat format = conversion.conversionData("com.wry.principle.inversion.dataconversion.remould.XmlData", dataSource);
XmlData data = (XmlData) format;
System.out.println(data.getData());
//后期可通过配置文件动态配置类名
format = conversion.conversionData("com.wry.principle.inversion.dataconversion.remould.XlstData", dataSource);
XlstData data2 = (XlstData) format;
System.out.println(data2.getData());
//文本文件数据源
dataSource = new TxtSource();
//后期可通过配置文件动态配置类名
format = conversion.conversionData("com.wry.principle.inversion.dataconversion.remould.XmlData", dataSource);
data = (XmlData) format;
System.out.println(data.getData());
//后期可通过配置文件动态配置类名
format = conversion.conversionData("com.wry.principle.inversion.dataconversion.remould.XlstData", dataSource);
data2 = (XlstData) format;
System.out.println(data2.getData());
}
}