浅析IoC控制反转的原理

Posted zhaozheng7758

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析IoC控制反转的原理相关的知识,希望对你有一定的参考价值。

        控制反转这个词可能是目前框架设计中提到最流行的词了,象SmallTalk、c++、java等都采用了这些原理进行实现。其中我们所熟知和常用的Spring Framework的核心亦不例外。其实早在2004年Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他总结出是依赖对象的获得被反转了。这个做何理解呢?我们知道目前的许多稍复杂的应用基本都是由两个或多个类通过彼此合作来实现具体业务逻辑的,这使得每个对象都需要与合作的对象之间保持引用的关系。如果这个获取过程靠自身来实现,将导致代码高耦合且难以测试。

        所谓控制反转可以理解为对象在创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也就是依赖注入到对象中。所以控制反转即关于一个对象如何获取它所依赖对象的引用,这个责任的反转。由之前自身去引用变为由调控系统进行注入。

        目前常用的IoC类型主要分为:基于接口的实现方式、基于Setter方法注入的方式和基于构造函数的方式。对于上述提到的3种类型的注入方式,它们之间的优缺点主要体现在:接口注入相对来说有一定的侵入性,对于复杂的应用来说需要编写大量的接口。当然对于较小的应用未必不是一种好的方式,当接口过多时需要较多的考虑接口间的配置及依赖关系。因此现在的应用较多于偏向于选择基于Setter方法的注入及构造函数的注入。对于二者间的优劣主要争论点在于你是倾向于在构造函数中传入多个参数,还是通过域设置Setter的方式。

        基于构造函数的方式有其自己的优势,它能够明确地创建出带有特定构造参数的对象,另外它对于创建一些在类中不需要经常变化的域有明显的优势。如果用setter方法来做这种事情会显得很不协调,但通常可以采用init的方法在创建时就将其初始化。当然对于某些类可能有很多的域,构造函数不可能包含所有的情况,而且其中能够包含的构造参数也是有限的,此时Setter方法注入即可以发挥其余地。

        下面我们以一个例子来分析一下依赖注入所带来的好处。假定一个想从电影列表中获取某个导演所有的影片列表的一个功能。我们该如何实现呢?

class MovieLister...
    public Movie[] moviesDirectedBy(String arg) 
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) 
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        
        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
    

对于上述的实现,有一个地方需要注意即finder.findAll()。它的功能是从罗列出所有的影片信息。那它的实现又是如何呢?按照常规的思路,我们一般会按如下的设计方式进行。

public intereface MovieFinder 
    List findAll();

在MovieLister中进行构造时再采用如下的方式进行调用:

Public class MovieLister 
MovieFinder movieFinder;
Public MovieLister() 
    movieFinder = new MovieFinderImpl();


        采用上述这种方案来设计时,明显的会将MovieLister与MovieFinder进行耦合起来,当可能MovieFinder有另外一种实现时,需要强制修改MovieLister来修改调用方式。换一种思路我们来考虑一下该问题,如果该MovieLister在调用该功能时,能够由一个配置或容器主动的将所需要的类注入进来,将使得双方间解耦。接下来的设计如下:


        采用该种解决方案后,其实是多了一个assemble,它用于协调双方的关系,当MovieLister在创建时,不需要显示的创建MovieFinder,而是在assemble中进行了依赖关系的定义。可以借用较为流行的Spring的实现方式如下:

Class MovieLister 
MovieFinder Finder; 
public void setFinder(MovieFinder finder) 
        this.finder = finder;


Assemble可以采用xml的形式来组织二者间的关系,具体配置如下:

<beans>
<bean id=”movieLister” class=”MovieLister”>
    <property name=”finder”>
    <ref local=”movieFinder”/>
</property>
</bean>
<bean id=”movieFinder” class=”MovieFinderImpl”>
</beans>
最终的功能调用方式如下所示:

public void testWithSpring() throws Exception 
        ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
        MovieLister lister = (MovieLister) ctx.getBean("movieLister");
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
       System.out.println(movies.length);
   
        当然为了实现解耦,并不是只有依赖注入可以完成,象serviceLocator的方式亦可以完成上述功能的解耦。它的基本思想即是有一个service locator可以定位到该应用所的所有服务。具体的设计可以参见如下的方式:


基于serviceLocator实现的代码片段如下:

class MovieLister 
MovieFinder finder = ServiceLocator.movieFinder();
….


class ServiceLocator 
private static ServiceLocator serviceLocator;
private MovieFinder movieFinder;

public ServiceLocator(MovieFinder movieFinder) 
    this.movieFinder = movieFinder;

public static void load(ServiceLocator arg) 
     if(serviceLocator == null) 
          serviceLocator = arg;


public static MovieFinder movieFinder() 
    return serviceLocator.movieFinder;



class Test 
public void configure() 
     ServiceLocator.load(new ServiceLocator(new MovieFinderImpl()));

public void testSimple() 
        configure();
        MovieLister lister = new MovieLister();
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    

参考文献:

【Inversion ofControl Containers and the Dependency Injection pattern】http://martinfowler.com/articles/injection.html

【The Dependency Inversion Principle】

http://www.objectmentor.com/resources/articles/dip.pdf

以上是关于浅析IoC控制反转的原理的主要内容,如果未能解决你的问题,请参考以下文章

Spring依赖注入浅析

IOC和DI到底是什么?

Spring 学习 2- IOC原理 控制反转/依赖注入

通俗化理解Spring3 IoC的原理和主要组件

java编程,spring里ioc和aop用啥原理实现的

Spring学习---Spring IoC容器的核心原理