很长一段时间里,我对控制反转和依赖注入这两个概念很模糊,闭上眼睛想一想,总有一种眩晕的感觉。但为了成为一名优秀的 Java 工程师,我花了一周的时间,彻底把它们搞清楚了。

01、紧耦合

在我们编码的过程中,通常都需要两个或者更多的类通过彼此的合作来实现业务逻辑,也就是说,某个对象需要获取与其合作对象的引用,如果这个获取的过程需要自己实现,代码的耦合度就会高,维护起来的成本就比较高。

我们来通过实战模拟一下。假如老王是少林寺的主持,他想让小二和尚去扫达摩院的地,代码可以这样实现。

小二类的代码如下所示:

public class Xiaoer {
    public void saodi() {
        System.out.println("小二我在扫达摩院的地");
    }
}

老王类的代码如下所示:

public class Laowang {
    public void mingling() {
        new Xiaoer().saodi();
    }
}

测试类的代码如下所示:

public class Test {

    public static void main(String[] args) {
        Laowang laowang = new Laowang();
        laowang.mingling();
    } }

Laowang 类的 mingling 方法中使用 new 关键字创建了一个 Xiaoer 类的对象——这种代码的耦合度就很高,维护起来的成本就很高,为什么这么说呢?

某一天,达摩院的地又脏了,老王主持想起了小二和尚,可小二和尚去练易筋经了,让谁去扫地呢,老王主持想起了小三和尚,于是 Laowang 类就不得不重新下一个新的命令,于是代码变成了这样:

public class Xiaosan {
    public void saodi() {
        System.out.println("小三我在扫达摩院的地");
    }
}
public class Laowang {
    public void mingling() {
        new Xiaoer().saodi();
    }     public void mingling1() {
        new Xiaosan().saodi();
    }
}

假如小三和尚去挑水了,老王主持没准要下命令给小四和尚去扫达摩院的地。这样下去的话,Laowang 这个类会疯掉的。

老王主持觉得自己堂堂一届高僧,下个扫地的命令竟然这样麻烦,他觉得很不爽。

02、控制反转

我们得替老王主持想个办法对不对?

不如把这个扫地的差事交给老王的师弟老方吧,老方负责去叫小二和尚还是小三和尚还是小四和尚去执行老王主持的命令。代码可以这样实现。

定义一个扫地和尚的接口,代码如下所示:

public interface Heshang {
    void saodi();
}

小二类的代码修改如下所示:

public class Xiaoer implements Heshang {

    @Override
    public void saodi() {
        System.out.println("小二我在扫达摩院的地");        
    }     public boolean isYijinjing() {
        // 星期三的时候小二和尚要练易筋经
        return false;
    }
}

小三类的代码修改如下所示:

public class Xiaosan implements Heshang {

    @Override
    public void saodi() {
        System.out.println("小三我在扫达摩院的地");        
    }
}

老方类的代码如下所示:

public class Laofang {
    public static Heshang getSaodiseng() {
        Xiaoer xiaoer = new Xiaoer();
        if (xiaoer.isYijinjing()) {
            return new Xiaosan();
        }
        return xiaoer;
    }
}

如果老方确认小二和尚在练易筋经,就叫小三和尚。

老王类的代码修改如下所示:

public class Laowang {
    public void mingling() {
        Laofang.getSaodiseng().saodi();
    }
}

测试类的代码不改变,如下所示:

public class Test {

    public static void main(String[] args) {
        Laowang laowang = new Laowang();
        laowang.mingling();
    } }

老王现在是不是省心多了,他只管下命令,该叫谁去扫达摩院的地由他师弟老方去负责。

我们替老王想的这个办法就叫控制反转(Inversion of Control,缩写为 IoC),它不是一种技术,而是一种思想——指导我们设计出松耦合的程序。

控制反转从词义上可以拆分为“控制”和“反转”,说到控制,就必须找出主语和宾语,谁控制了谁;说到反转,就必须知道正转是什么。

你看,在紧耦合的情况下,老王下命令的时候自己要通过 new 关键字创建依赖的对象(小二和尚或者小三和尚);而控制反转后,老王要找的扫地和尚由他师弟老方负责,也就是说控制权交给了老方,是不是反转了呢?

03、依赖注入

依赖注入(Dependency Injection,简称 DI)是实现控制反转的主要方式:在类 A 的实例创建过程中就创建了依赖的 B 对象,通过类型或名称来判断将不同的对象注入到不同的属性中。大概有 3 种具体的实现形式:

1)基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。

老王类的代码修改如下所示:

public class Laowang {
    private Heshang saodiseng;     public Laowang(Heshang saodiseng) {
        this.saodiseng = saodiseng;
    }
    public void mingling() {
       this.saodiseng.saodi();
    }
}

测试类的代码修改如下所示:

public class Test {

    public static void main(String[] args) {
        Laowang laowang = new Laowang(new Xiaosan());
        laowang.mingling();
    } }

这时候,控制权掌握在测试类的手里,它决定派小二和尚还是小三和尚去执行老王的扫地命令。

2)基于 set 方法。实现特定属性的 public set 方法,让外部容器调用传入所依赖类型的对象。

老王类的代码修改如下所示:

public class Laowang {
    private Heshang saodiseng;     public Heshang getSaodiseng() {
        return saodiseng;
    }     public void setSaodiseng(Heshang saodiseng) {
        this.saodiseng = saodiseng;
    }     public void mingling() {
       this.getSaodiseng().saodi();
    }
}

测试类的代码修改如下所示:

public class Test {

    public static void main(String[] args) {
        Laowang laowang = new Laowang();
        Xiaosan xiaosan = new Xiaosan();
        laowang.setSaodiseng(xiaosan);
        laowang.mingling();
    } }

这时候,控制权仍然掌握在测试类的手里,它决定派小二和尚还是小三和尚去执行老王的扫地命令。

3)基于接口。实现特定接口以供外部容器注入所依赖类型的对象,这种做法比较构造函数和 set 方法更为复杂,这里就此略过。

可能有人会把控制反转等同于依赖注入,但实际上它们有着本质上的不同:控制反转是一种思想,而依赖注入是实现控制反转的一种形式。

04、Spring 框架

当我们搞清楚控制反转和依赖注入的概念后,就可以顺带了解一下大名鼎鼎的 Spring 框架。控制反转是 Spring 框架的核心,贯穿始终。Spring 中依赖注入有两种实现方式:set 方式(传值方式)和构造器方式(引用方式)。

首先,我们需要在 pom.xml 文件中加入 Spring 的依赖项,代码如下所示:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>

其次,我们将 Laowang 类修改为如下内容:

public class Laowang {
    private Heshang saodiseng;     public Laowang(Heshang saodiseng) {
        this.saodiseng = saodiseng;
    }
    public void mingling() {
       this.saodiseng.saodi();
    }
}

然后,我们创建一个 Spring 的配置文件 application.xml,内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">   <bean id="laowang" class="com.cmower.java_demo.ioc.Laowang">
    <constructor-arg ref="saodiseng" />
  </bean>   <bean id="saodiseng" class="com.cmower.java_demo.ioc.Xiaosan" /> </beans>

通过 元素配置了两个对象,一个老王主持,一个小三和尚,使用 元素将小三和尚作为老王主持的构造参数。

准备工作完成以后,我们来测试一下,代码示例如下:

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        Laowang laowang = (Laowang) context.getBean("laowang");
        laowang.mingling();
    } }

你看,我们将控制权交给了 IoC 框架 Spring,这样也可以完美的解决代码耦合度较紧的问题。

05、最后

总结一下:

1)控制反转是一种在软件工程中解耦合的思想,把控制权交给了第三方,在运行的时候由第三方决定将具体的依赖对象“注入”到调用类的对象中。

2)依赖注入可以作为控制反转的一种实现方式,将实例变量传入到一个对象中去。

3)通过 IoC 框架,类 A 依赖类 B 的强耦合关系可以在运行时通过容器建立,也就是说把创建 B 实例的工作移交给容器,类 A 只管使用就可以。

Java:控制反转(IoC)与依赖注入(DI)的更多相关文章

  1. iOS控制反转(IoC)与依赖注入(DI)的实现

    背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大 ...

  2. 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。

    轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战   ...

  3. 控制反转IOC与依赖注入DI

    理解 IOC  http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.html IOC 相关实例      的http:// ...

  4. 控制反转(Ioc)和依赖注入(DI)

    控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...

  5. 控制反转IOC与依赖注入DI【转】

    转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...

  6. 【转载】浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

    原文地址 http://blog.csdn.net/briblue/article/details/75093382 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来 ...

  7. 控制反转IOC与依赖注入DI - 理论篇

    学无止境,精益求精 十年河东十年河西,莫欺少年穷 昨天是五一小长假归来上班的第一天,身体疲劳,毫无工作热情.于是就看看新闻,喝喝茶,荒废了一天 也就在昨天,康美同事张晶童鞋让我学习下IOC的理论及实现 ...

  8. 依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

    原文: https://blog.csdn.net/briblue/article/details/75093382 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来 ...

  9. Spring框架学习笔记(1)——控制反转IOC与依赖注入DI

    Spring框架的主要作用,就是提供了一个容器,使用该容器就可以创建并管理对象.比如说Dao类等,又或者是具有多依赖关系的类(Student类中包含有Teacher类的成员变量) Spring有两个核 ...

  10. 20181123_控制反转(IOC)和依赖注入(DI)

    一.   控制反转和依赖注入: 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节) 如果要想做到控制反转(IOC), 就必须要使 ...

随机推荐

  1. JS处理时间戳、前台拿到日期时间戳,时间戳转日期格式

    今晚做分页的时候,遇到后台往前台传日期类型,会出现这种情况,好像是微软为了解决操作系统兼容性问题,将日期全部转换为从1970年1月1日至现在时间的时间戳.为了解决这个问题,特意百度了一番,整理了处理日 ...

  2. 吐槽:那些Java设计中不得不说的槽点

    1. 求长度各有千秋 你是否曾经在面试的时候,经常被问到:数组有没有 length() 方法?字符串有没有 length() 方法? 集合有没有 length() 方法? 面对这个问题,那么不得不吐槽 ...

  3. 如何配置MySQL

    解压绿色版mysql,并改名为mysql5.7 运行CMD(管理员版本,否则没有权限) 运行完后 然后就把地址改为你存放mysql5.7下的bin目录 对于新版mysql5.7没有了data目录,我们 ...

  4. SQL Server 2016 + AlwaysOn 无域集群

    目录 AlwaysOn 搭建 WSFC 配置计算机的 DNS 后缀 安装故障转移集群 验证集群 创建集群 创建文件共享见证 配置 AlwaysOn 新建可用性组 创建侦听器 可读副本的负载均衡 主角色 ...

  5. 网络下载器 Pan Download v2.0.5 Lite 绿色便携版

    下载地址:点我 基本介绍 PanDownload最新版是一款能够快速下载百度网盘内资源的强大工具.PanDownload最新版能够无限速高速下载,满速下载百度云盘里的各种资源.而且PanDownloa ...

  6. Lockey的沙雕低错集锦(未完待续~自己提升用)

      (嘿嘿 (^~ ^)) 1. bitset<2100>a[2100]    a[x]|=(1<<x)   (1<=x<=2100)  使用时忘了x的范围,额,应 ...

  7. 关于CMTS设备的一些备忘

    博主工作内容包括cable modem,对CM的工作方式有一些了解,但是对CMTS头端怎么带动一个用户小区长久以来一直是一头雾水.今天找了些资料,对这块有了一些了解,并把自己的理解总结下来. 比如我家 ...

  8. 【深入浅出-JVM】(8):TLAB

    概念 TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区(线程私有分配区,私有分配,公共查看),占用 Eden 区(缺省 Eden 的1%),默认开启,JVM ...

  9. 关于int和integer

    大家可以看一下下面这个java程序的运行结果 int k = 1; int l = 1; System.out.println(k == l); int a = 128; int b = 128; S ...

  10. [记录] Linux登录前后提示语

    Linux登录前后提示语 /etc/issue 本地(虚拟控制台KVM等)登录前提示语,支持转义字符 /etc/issue.net 远程(telnet,ssh)登录前提示语,不支持转义字符 /etc/ ...