浅析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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值