控制反转是一个常见的面向对象技术,它主要应用在框架中。如 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 有关“依赖注入”的介绍,需要更多了解可以点击此链接