现在的位置: 首页 > 综合 > 正文

控制反转(IoC) 的理解

2013年03月05日 ⁄ 综合 ⁄ 共 3147字 ⁄ 字号 评论关闭

控制反转是一个常见的面向对象技术,它主要应用在框架中。如 GUI框架,Spring框架等,这些框架为设计特定的应用程序提供了一般性的步骤,框架把核心的控制流程集成于自身,仅仅把一些具体的实现任务交给用户(程序员即为框架的用户),正是由于这些框架的产生才有必要用到控制反转技术。 也就是说,如果GUI框架仅仅被一个人使用,那么完全不用控制反转也行,因为此框架仅为一个人使用,这个人使用的类是什么样子的,有什么样的类,这个人心里很明白,它可以把这些类写死在代码中。 但这个框架如果被许多人使用,就需要考虑控制反转问题了。看例子:

下面例子的作用是:提供一个导演的名子给 moviesDirectedBy 方法,它就能返回此导演所导演过的所有电影。

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 是个什么东西,它是如何实现的?如下代码所示:

public interface MovieFinder {
    List findAll();
}

class MovieLister...
  private MovieFinder finder;
  public MovieLister() {
    finder = new ColonDelimitedMovieFinder("movies1.txt");
  }

finder 可以看成是一个接口即MovieFinder,在 MovieLister类的构造函数中对它进行了实例化,即

finder = new ColonDelimitedMovieFinder("movies1.txt");

ColonDelimitedMovieFinder类从一个以冒号分隔的 文本文件中读取电影列表。

从现在来看,上面的实现都算顺利,没有发现不托之处,现在问题来了:

我的一个朋友想使用我的代码,即想使用我的 MovieLister 类, 我的MovieLister类现在有了"框架"的味道了。但朋友的 MovieFinder如何实现我可不确定,它也许把电影列表存储在XML文件中或者直接从网上请求的,那么MovieLister如何去实例化 MovieFinder呢,没有办法直接实现。现在类的关系如下图:

MovieLister需要实例化一个 MovieFinderImpl 来找出所有的电影列表,而现在这个 MovieFindImpl 是个什么样子的根本不确定,那么这个问题如何解决。

解决办法如下图所示:

引入一个 Assembler 的装配类,由这个类来告诉 MovieLister  MovieFinderImpl具体是什么,这个装配过程在运行时通过Java反射机制来完成,这个装配的过程也就是Dependency Injection(依赖注入), 依赖注入有三种方式,分别为:

1 通过构造函数依赖注入

2 通过配置文件依赖注入

3 通过接口依赖注入

在这里只讨论第2种情况:通过配置文件依赖注入(这也是Spring 框架常用的方式)

class MovieLister...
    private MovieFinder finder;
  public void setFinder(MovieFinder finder) {
    this.finder = finder;
  }

class ColonMovieFinder...
    public void setFilename(String filename) {
        this.filename = filename;
    }

<beans>
        <bean id="MovieLister" class="spring.MovieLister">
            <property name="finder">
                <ref local="MovieFinder"/>
            </property>
        </bean>
        <bean id="MovieFinder" class="spring.ColonMovieFinder">
            <property name="filename">
                <value>movies1.txt</value>
            </property>
        </bean>
</beans>

public void testWithSpring() throws Exception {
        ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
        MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }

配置文件就是指 上上图的 XML文件,在配置文件中声明了 MovieLister,同时也定义了 MovieLister 所关联的 MovieFinder;配置文件中也声明了 MovieFinder,这个MovieFinder 标签指明了一个 MovieFinderImpl即 ColonMovieFinder,整个 MovieLister框在运行时会动态的加载 XML 文件,并从中提取出 MovieLister和MovieFinder 的相关信息,并用反射机制实例化了具体的 MovieFinderImpl。

在这里有个重点:

MovieLister类的编写必须按照一定的格式,框架才能为其“注入”适当的类,这个格式就是:

class MovieLister...
    private MovieFinder finder;
  public void setFinder(MovieFinder finder) {
    this.finder = finder;
  }

即类的编写必须写一个 private MovieFinder finder  并写一个 setFinder 的方法(名子必须是这样)。

其它方面的思考:

本文研究的初衷不是针对 Spring,而是针对 Android, Android的UI设计主要采用 XML 的方式,其实这也是“依赖注入”的形式。可以想一想程序中的每一个 Activity,Service等组件都是要在 manifest.xml中声明的,不然程序就会出错。也就是Android框架把你 XML 文件中的 Activity等注入到 Android的 framework 中,这样系统就能正常运行起来了。注入 Android 框架中的 Activity 必须从 android.app.Activity
继承,而且还要实现 onCreate/onResume/onPause 等方法,这就相当于 MovieFinder 的具体类必须实现 MovieFinder接口一样,这是框架内部需要调用的方法。

关于 Android总结为一句话帮助自己记忆:

manifest.xml配置了Activity,框架读取xml并把注册的Activity注入到框架中,并调用规定好的 Activity 的生命周期方法进行动作。

本文参考:http://www.martinfowler.com/articles/injection.html   有关“依赖注入”的介绍,需要更多了解可以点击此链接

抱歉!评论已关闭.