你真的理解什么是IOC了吗?

1,047 阅读6分钟

前言

好哥哥们,在正文之前需要想这么一个问题。如果不用Spring 的话,你还会写代码吗?我相信有好多功能脱离了Spring实现起来会非常麻烦,甚至简单的增删改查实现起来都是有困难的。所有我们在狂吹Spring多好多好的同时,正是因为这些好的框架把一些复杂实现都隐藏起来了,从而导致很多东西攻城狮只是知道运用而不知道简单的一两行代码后面到底有什么。

对于整个项目来说,这样做的好处是非常多的。但是对于攻城狮来说,Spring或者其他框架就是一个黑盒子。如果不打开这个黑盒,那我们永远只会运用而已,是的仅仅会用而已。如果好哥哥们不甘心于此,那就和安酱一起打开这些黑盒子把!当然今天这篇的话也只是相关Spring IOC

概述

这里借助精通Spring+4.x++企业应用开发实战 书中的关于IOC概念的一个例子。在张之亮的贺岁大片墨攻中,有一个刘德华所饰演的墨者革离到达梁国都城时城门叩问的场景。

城上守军问道:来者何人?
刘德华回答道:墨者革离!

通过以上城门叩问场景,使用 Java 来编写剧本是个怎么样子的。

直接写死

这种方式具体的饰演者直接侵入到了剧本中,使得演员和剧本耦合在一起。

/**
 * 墨攻剧本
 */
public class MoAttack {
    /**
     * 城门叩问
     */
    public void gatesAsk() {
    	// 演员直接侵入了剧本,也就是革离必须是刘德华
        LiuDeHua liuDeHua = new LiuDeHua();
        liuDeHua.responseAsk("墨者革离");
    }
}

角色抽离

正常来讲,剧本的故事情节发展是不会受演员的而变动的,创造剧本时应该围绕角色本身,而不是演员。这样才可以在剧本投拍时选择合适的演员,而非将角色绑定在一人之上,所以我们将角色(墨者革离)本身抽离出来成为一个接口,不关心是谁来演。

/**
 * 墨攻剧本
 */
public class MoAttack {
    /**
     * 城门叩问
     */
    public void gatesAsk() {
    	// 将角色抽离,不关心具体的饰演者
        Geli geli = new LiuDeHua();
        // 通过接口展开剧情
        geli.responseAsk("墨者革离");
    }
}

功能抽离

有细心的好哥哥会发现,通过角色抽离剧本还是会依赖于具体的饰演者,上面代码中依然指定了LiuDeHua来作为具体的饰演者,整个剧本依然还是演员耦合起来了。那该怎么做呢?

我们是不是可以将写剧本和拍电影两个功能分离开,编剧只负责写剧本,而拍电影则交给导演。通过以下代码,编剧可以不依赖于任何的饰演者,导演只要将具体的饰演者放到剧本中即可。

/**
 * 墨攻剧本
 */
public class MoAttack {

    // 革离角色,无具体的实现,由导演在拍电影时自己选择
    private Geli geli;

    public MoAttack(Geli geli) {
        this.geli = geli;
    }

    /**
     * 城门叩问
     */
    public void gatesAsk() {
        geli.responseAsk("墨者革离");
    }
}

问题点

通过以上城门叩问场景的举例,我们实现了剧本和饰演者之间的抽离。跳出这个例子,我们从代码的角度来分析一下存在的问题点。

  1. 直接写死和角色抽离的方式在每次执行城门叩问方法时都会创建具体的饰演者对象,这样无疑从性能和资源的浪费消耗都是巨大的。

  2. 通过角色抽离的方式在每次执行城门叩问方法时都要明确知道是有那个具体的饰演者对象(LiuDeHua),并且只能是这个对象。

  3. 通过功能抽离的方式把角色抽离成公共的成员变量,正常一部剧一个场景下对应角色的饰演者只会有一个,那怎么保证Geli这个角色的单例性。

  4. 导演每次通过功能抽离的方式来投拍时,都是需要指定一个具体的饰演者对象,一部剧一个场景下对应角色的饰演者只会有一个,然而在拍这个场景时可能会拍多次,每一次又都是同一个人,是否能消除这个重复的动作。

解决方案

  1. 第一个问题其实已经通过功能抽离的方式解决了,不在需要new具体的饰演者对象。

  2. 第二个问题通过功能抽离的方式也解决了,让剧本只关心剧情而不关心演员。

  3. 第三个问题设计到了成员变量Geli,只要保证MoAttack是单例的也就可以保证Geli这个对象也是单例的。

  4. 第四个问题的话需要将饰演者和角色对应的关系在投拍时就要确定好,也就是说在程序启动时就指定依赖关系,初始化时就把LiuDeHua设置成Geli的默认饰演者

IOC

通过功能抽离的方式实现的城门叩问,实际上是由导演协调了剧本、角色、饰演者三者的关系。如果不通过导演这个角色,那在电影投拍前剧本的剧情就必须要知道对应角色的饰演者是谁,才能继续展开剧情。

这时我们在来看一下IOCIOC 字面意思是控制反转。在城门叩问这个场景中 ,控制指的是导演对GeLi角色扮演者的选择权。而反转指的是将这个控制权从剧本中移除,把这个控制权转交到导演手中。也就是说把某一个接口具体实现类的选择控制权从调用类中移除,转交给第三方来决定,类似于使用功能抽离的方式。Spring容器就是在程序初始化时通过对 Bean 的配置来指定这个依赖关系从而做到控制权的反转(控制权交给了Spring)。

因为IOC 确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物Martin Fowler 提出DI(Dependency Injection,依赖注入)的概念用来代替IOC,即让调用类对某一接口实现类的依赖关系有第三方(容器或者协作类)注入,以移除调用类对某一接口实现类的依赖。

总结

这一篇的话主要通过城门叩问的例子来理解IOC,实际上都是理论性的东西,理解了上面的例子对于在去看IOC的实际上还是没什么大问题。因为理解这个概念真的很重要,这个会一直存在在我们后续的开发中。看不懂的好哥哥多看几遍就懂了,还不懂的那就是我写的有问题(手动狗头)。

最后关注安安酱,一个热衷于分享干货的公众号。现在关注领取即可Sping源码分析相关资料。