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

01、紧耦合

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

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

小二类的代码如下所示:

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

老王类的代码如下所示:

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

测试类的代码如下所示:

  1. public class Test {
  2.     public static void main(String[] args) {
            Laowang laowang = new Laowang();
            laowang.mingling();
        }
  3. }

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

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

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

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

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

02、控制反转

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

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

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

  1. public interface Heshang {
        void saodi();
    }

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

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

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

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

老方类的代码如下所示:

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

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

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

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

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

  1. public class Test {
  2.     public static void main(String[] args) {
            Laowang laowang = new Laowang();
            laowang.mingling();
        }
  3. }

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

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

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

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

03、依赖注入

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

04、Spring 框架

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

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

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

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

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

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

  1. <?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">
  2.   <bean id="laowang" class="com.cmower.java_demo.ioc.Laowang">
        <constructor-arg ref="saodiseng" />
      </bean>
  3.   <bean id="saodiseng" class="com.cmower.java_demo.ioc.Xiaosan" />
  4. </beans>

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

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

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

你看,我们将控制权交给了 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. RT-thread线程创建:动态线程与静态线程

    本文介绍了如何创建一个动态线程和一个静态线程 RT-thread版本:RT-thread system 3.1.0 开发环境:MDK5 为了编程方便,创建了sample1.c文件,然后添加到工程中 话 ...

  2. Linux搭建基于BIND的DNS服务器

    Linux搭建基于BIND的DNS服务器   实验目标: 通过本实验掌握基于Linux的DNS服务器搭建. 实验步骤: 1.安装BIND 2.防火墙放通DNS服务 3.编辑BIND的主配置文件 4.编 ...

  3. [apue] 多进程管道读写的一些疑问

    对于一对一的pipe: 1) 写进程关闭写管道后,读进程继续读管道会导致read返回0: 2) 读进程关闭读管道后,写进程继续写管道会激发SIGPIPE信号,若捕获,则write返回-1: 而对于多对 ...

  4. CSU 1811: Tree Intersection(线段树启发式合并||map启发式合并)

    http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1811 题意:给出一棵树,每一个结点有一个颜色,然后依次删除树边,问每次删除树边之后,分开的两个 ...

  5. restapi(0)- 平台数据维护,写在前面

    在云计算的推动下,软件系统发展趋于平台化.云平台系统一般都是分布式的集群系统,采用大数据技术.在这方面akka提供了比较完整的开发技术支持.我在上一个系列有关CQRS的博客中按照实际应用的要求对akk ...

  6. Java实现异步调用

    一.创建线程 @Test public void test0() throws Exception { System.out.println("main函数开始执行"); Thre ...

  7. Touch Bar 废物利用系列 | 在触控栏上显示 Dock 应用图标

    都说 Intel 第八代 CPU 对比上代是牙膏不小心挤多了,而配备第八代 CPU 的 MacBook Pro,只有 Touch Bar 版本,虽然贵了一点,但就一个字 -- 买! 收到电脑后,兴冲冲 ...

  8. WinForm控件之【LinkLabel】

    基本介绍 超链接标签控件,随处可见应用极为广泛,一般用作触发指定链接跳转指定页面等操作. 常设置属性.事件 ActiveLinkColor:用户单击超链接时超链接显示的颜色: LinkColor:超链 ...

  9. MyBatis从入门到精通:insert用法

    2.4.1 简单的insert方法 1.接口类中的方法: int insert(SysUser sysUser); 2.映射文件中的修改: <!-- insert标签包含如下的属性: id: p ...

  10. VirtualBox下安装Linux系统

    Linux 开源的类Unix操作系统,拥有图形界面文字界面,旗下发行版不胜其数(Ubuntu.Deepin.CentOS...)在互联网公司中Linux服务器大多是用的CentOS系统,其他Linux ...