控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
这是百度上的定义。我结合知乎、CSDN加上个人见解开始分析一下这句话。
要了解控制反转 我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Principle )。
依赖倒置原则
传统的,由高层模块调用底层模块的方法。这样当底层模块方法变化高层也需要修改。例如:

当底层的轮胎类需要如下修改时:

此时上层就无法调用Tire()这个无参构造器,为扩展修改就需要从下到上修改。如下:

由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。
那么依赖倒置原则做了什么呢?它规定高层模块定义接口、底层模块依赖于抽象(中间层/接口)。
现在你可能不是很理解这句话,没事,现在我们看看例子。
如你所见现在Car的上层类依赖下层类的实现,我们把它反过来把类改写成这样:

现在高层不再依赖低层依赖。
通过上面我们了解了依赖倒置原则,以及为什么传统的编码方式存在高耦合度。那么为了实现依赖倒置原则,就有人提出了控制反转(IoC)这一思路。
无论下层怎么变化上层都能控制住。并且此时上层就不再依赖下层的实现,此时就实现的Inversion(反转)。这就是控制反转(Ioc)。
看完了百度,我们来看看Spring官网的描述:

这里我们只需要看标红的部分。上文中提到IoC是实现依赖倒置原则的思路,那么怎么实现IoC呢?DI。
是什么DI?
上面的例子中其实就是依赖注入(Dependency Injection),用构造函数注入依赖。其实还有两种DI方式:接口注入、Setter注入。

看到这里你应该能理解什么控制反转和依赖注入了。那什么是控制反转容器(IoC Container)呢?其实上面的例子中,对车类进行初始化的那段代码发生的地方,就是控制反转容器。
控制反转容器(IoC Container)

显然你应该也观察到了,因为采用了依赖注入,在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。这个容器可以自动对你的代码进行初始化,你只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。
IoC Container的第二个好处是:我们在创建实例的时候不需要了解其中的细节。在上面的例子中,我们自己手动创建一个车instance时候,是从底层往上层new的:

这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。
而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):

这里IoC Container可以直接隐藏具体的创建实例的细节,在我们来看它就像一个工厂:

我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。
至此我们了解从依赖倒置原则到控制反转IoC到IoC的实现依赖注入DI。
那么怎么IoC容器具体是怎么实现DI的呢?
IoC中最基本的技术就是“反射(Reflection)”编程。这种编程方式可以让对象在生成时才决定到底是哪一种对象。

分社其实内容蛮多的,但是基本上所有容器都是通过这样的方式在运行时操作对象的。这里有兴趣的话可以自己去了解一下,可以百度、知乎、书籍,总之怎么方便怎么来。
希望对你有帮助。
网友评论