、概述
所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合,并由此引申出IoC、DI以及Ioc容器等概念。
2、意图
3、正文
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
一个一个来说吧,首先先来了解下"依赖倒置原则(DIP)":
举个生活中的小例子:
取过钱的朋友都知道,只要我们手上有一张银行卡,我们就可以到各个银行的ATM机上去取款,在这个场景中ATM机器属于高层模块,我们手上的银行卡属于底层模块。
在ATM机上提供了一个卡槽插口(接口),供各种银行卡插入使用,在这里ATM机不依赖于具体的哪种银行卡,它只规定了银行卡的规格,只要我们手上的银行卡满足这个规格参数,我们就可以使用它。
转换下概念,也就是:
高层模块不依赖于底层模块,而底层模块依赖于高层模块的接口(高层模块定义接口,底层模块负责实现)。
高层模块(接口):抽象 底层模块(实现接口):实现 ==>两者应该依赖于抽象,抽象(高层)不依赖实现(底层),实现(底层)依赖于抽象(高层)。
再来举个例子:
1、如果依赖不导致将会出现:高层模块依赖于底层模块,也就是说底层变成了抽象,高层需要实现抽象出来的所有接口,一旦底层出现新的模块,则就需要去修改高层的模块,破坏了开放-封闭原则。
2、如果依赖倒置将会出现:底层模块依赖于高层模块,也就是说高层变成了抽象,底层只需要去实现高层的接口就行,一旦底层出现新的模块,则高层模块就不需要去修改(定义抽象接口不变)。
由此可见DIP的优点:
系统更柔韧:可以修改一部分代码而不影响其他模块。
系统更健壮:可以修改一部分代码而不会让系统崩溃。
系统更高效:组件松耦合,且可复用,提高开发效率。
接下来说下"控制反转(Ioc)":
DIP是一种软件设计原则,是告诉我们模块之间应该是怎样的一种关系,那Ioc就是具体的一种软件设计模式,告诉我们应该如何去做,才能做到程序间的解耦。
Ioc(控制反转)为高、低层模块之间提供了抽象,也就是第三方系统,也就是依赖对象(底层对象)不在依赖的模块中(高层模块)中直接创建对象,而是把创建对象的权利交给第三次Ioc容器来创建,然后再把对象交给依赖模块(联想刚刚取钱的例子,ATM机器是高层模块,它自身并没有决定要插入哪个银行的银行卡,比如建行,农行,要插入什么卡的决定权在于我们(也就是第三方),我们插入什么行的卡,它就给我们什么银行的服务)。
来个具体代码感受下Ioc的好处:(订单系统,底层操纵类是基于Mysql数据库的)
MysqlHelper.java
1 package com.lcw.dip.test; 2 3 public class MysqlHelper { 4 5 public void add(){ 6 System.out.println("增加订单.."); 7 } 8 9 public void delete(){ 10 System.out.println("删除订单.."); 11 } 12 13 public void update(){ 14 System.out.println("修改订单.."); 15 } 16 17 public void find(){ 18 System.out.println("查询订单.."); 19 } 20 }
Order.java
1 package com.lcw.dip.test; 2 3 public class Order { 4 private MysqlHelper helper = new MysqlHelper(); 5 6 public void addOrder() { 7 this.helper.add(); 8 } 9 10 public void delOrder(){ 11 this.helper.delete(); 12 } 13 14 public void updateOrder(){ 15 this.helper.update(); 16 } 17 18 public void FindOrder(){ 19 this.helper.find(); 20 } 21 }
DipTest.java
package com.lcw.dip.test; /** *DIP(Dependence Inversion Principle)依赖倒置原则 * @author Balla_兔子 * */ public class DipTest { public static void main(String[] args) { Order order=new Order(); order.addOrder(); } }
看下操作效果:
Perfect,完美!!
但如果现在突然业务需求要改换成Access数据库,这时改怎么办呢?
传统的做法,我们需要再去编写一个关于Access的数据库操纵类,然后修改下Order类里的代码,把实例化对象修改成Access类。
那要是过几天又要改成Oracle数据库呢?
。。。。。反反复复,周而复始,烦!
有没有什么办法可以解决这个繁琐的问题呢?答案是必须有!不然我就不用打这么多字了~~
接下来依赖注入(DI)就派上用场了:
依赖注入是实现Ioc的一种重要方式,将依赖的对象的创建权交给外部(第三方)来处理,而不是在自身new出一个实例。
例如上面的添加订单例子,我们在创建数据库操纵对象的时候是在Order类中直接new出,这样有个很不好的地方就是,一旦数据库变动,则我们还要去修改Order类,很显然这是不可取的,违反了开放-封闭原则 。
那我们应该怎么做呢?答案很明显就是利用DI(依赖注入),将创建对象的权利交给外部(第三方)实现,然后再传递给需要调用对象的模块,也就是高层模块。
传递注入的方式有三种:
1、构造注入:顾名思义利用构造方法注入
2、setter方法注入:在需要注入的类里提供一个setter方法
3、接口注入:因为具有代码侵入性,一般很少用,前2种居多
说了这么多,上代码直接看实例吧
DbHelper.java
1 package com.lcw.dip.test; 2 3 public class DbHelper { 4 5 public void add(){ 6 System.out.println("增加订单.."); 7 } 8 9 public void delete(){ 10 System.out.println("删除订单.."); 11 } 12 13 public void update(){ 14 System.out.println("修改订单.."); 15 } 16 17 public void find(){ 18 System.out.println("查询订单.."); 19 } 20 }
Order.java
1 package com.lcw.dip.test; 2 3 public class Order { 4 //private MysqlHelper helper = new MysqlHelper(); 5 private DbHelper helper; 6 public Order(DbHelper helper){//提供构造方法,注入属性 7 this.helper=helper; 8 } 9 10 public void addOrder() { 11 this.helper.add(); 12 } 13 14 public void delOrder(){ 15 this.helper.delete(); 16 } 17 18 public void updateOrder(){ 19 this.helper.update(); 20 } 21 22 public void FindOrder(){ 23 this.helper.find(); 24 } 25 }
DipTest.java
1 package com.lcw.dip.test; 2 3 /** 4 *DIP(Dependence Inversion Principle)依赖倒置原则 5 * @author Balla_兔子 6 * 7 */ 8 public class DipTest { 9 public static void main(String[] args) { 10 //Order order=new Order(); 11 DbHelper helper=new DbHelper(); 12 Order order=new Order(helper);//注入DbHelper对象 13 order.addOrder(); 14 } 15 16 }
效果依旧,这样就很方便我们下次修改了,比如我们要换成Access数据库,那么这次我们只需要去修改数据库操纵类DbHelper就可以了,就不必要去动Order类了。
再来看下利用setter方法的注入:
DbHelper.java 数据库操作底层类不变
Order.java
1 package com.lcw.dip.test; 2 3 public class Order { 4 // private MysqlHelper helper = new MysqlHelper(); 5 // private DbHelper helper; 6 // public Order(DbHelper helper){//提供构造方法,注入属性 7 // this.helper=helper; 8 // } 9 10 private DbHelper helper; 11 public void setHelper(DbHelper helper) { 12 this.helper = helper; 13 } 14 15 public void addOrder() { 16 this.helper.add(); 17 } 18 19 public void delOrder(){ 20 this.helper.delete(); 21 } 22 23 public void updateOrder(){ 24 this.helper.update(); 25 } 26 27 public void FindOrder(){ 28 this.helper.find(); 29 } 30 }
DipTest.java
1 package com.lcw.dip.test; 2 3 /** 4 *DIP(Dependence Inversion Principle)依赖倒置原则 5 * @author Balla_兔子 6 * 7 */ 8 public class DipTest { 9 public static void main(String[] args) { 10 // Order order=new Order(); 11 // DbHelper helper=new DbHelper(); 12 // Order order=new Order(helper);//注入DbHelper对象 13 DbHelper helper=new DbHelper(); 14 Order order=new Order(); 15 order.setHelper(helper); 16 order.addOrder(); 17 } 18 19 }
效果依旧:
最后来说下关于Ioc容器:
在上面的例子中,我们都是通过手动的方式来创建依赖对象,然后在手动传递给被依赖模块(高层),但对于大型的项目来说,各个组件之间的依赖关系式非常复杂的,如果我们还是用手动来创建依赖对象并且手动注入是个相当繁杂的一个工作,而且还容易出错,甚至出现不可控状态。
因此Ioc容器就这样诞生了,也就是DI的一个框架,用来简化我们的操作,Ioc容器可以做到动态创建、注入对象,对象的生命周期管理,映射依赖关系等。
Ioc容器有很多比如:PicoContainer,JBoss Microcontainer,Soto,Spring等。
总结一下:
DIP是软件设计的一种思想,IoC则是基于DIP衍生出的一种软件设计模式。
DI是IoC的具体实现方式之一,使用最为广泛。
IoC容器是DI注入的框架,它管理着依赖项的生命周期以及映射关系。