很长一段时间里,我对控制反转和依赖注入这两个概念很模糊,闭上眼睛想一想,总有一种眩晕的感觉。但为了成为一名优秀的 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. MySQL数据库修改字段的长度

    数据库版本:5.7.22 使用DDL语句:alter table 表名 modify 字段名 字符类型(长度) 例如: alter table db2.admin modify password );

  2. HDU 3065:病毒侵袭持续中(AC自动机)

    http://acm.hdu.edu.cn/showproblem.php?pid=3065 题意:中文题意. 思路:直接插入然后用一个数组记录id和cnt,因为n只有1000,可以开一个数组判断第几 ...

  3. Codeforces 755A:PolandBall and Hypothesis(暴力)

    http://codeforces.com/problemset/problem/755/A 题意:给出一个n,让你找一个m使得n*m+1不是素数. 思路:暴力枚举m判断即可. #include &l ...

  4. 关于ArrayList的扩容机制

    关于ArrayList的扩容机制 ArrayList作为List接口常用的一个实现类,其底层数据接口由数组实现,可以保证O(1) 复杂度的随机查找, 在增删效率上不如LinkedList,但是在查询效 ...

  5. django基础知识之HttpReqeust对象:

    HttpReqeust对象 服务器接收到http协议的请求后,会根据报文创建HttpRequest对象 视图函数的第一个参数是HttpRequest对象 在django.http模块中定义了HttpR ...

  6. 解决thinkphp在开发环境下文件模块找不到的问题

    win10系统下,phpstudy开发环境下小问题描述: 找不到public公共模块. Not Found The requested URL /public/admin/login.html was ...

  7. 你必须了解的java内存管理机制(四)-垃圾回收

    本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) ...

  8. 【学习笔记】动态规划—斜率优化DP(超详细)

    [学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...

  9. Devops-运维效率之数据迁移自动化

    overmind系统上线三个月,累计执行任务800+,自动审核执行SQL超过5000条,效率提升相当明显,离"一杯咖啡,轻松运维"的目标又进了一步. 写在前边 overmind系统 ...

  10. TreeMap原理实现及常用方法

    目录 一. TreeMap概述 二. 红黑树回顾 三. TreeMap构造 四. put方法 五. get 方法 六. remove方法 七. 遍历 八. 总结 前面我们分别讲了Map接口的两个实现类 ...