Inversion of Control Containers and the Dependency Injection pattern扎翻譯
原文地址:控制反轉(zhuǎn)和依賴注入
參考1:Object Builder Application Block
參考2:深入理解依賴注入
總結(jié)
依賴注入是一個模式使兔,一個模糊的概念,而控制反轉(zhuǎn)是實現(xiàn)依賴注入的一種實現(xiàn)方法。
實現(xiàn)控制反轉(zhuǎn)有三種方式:
- 構(gòu)造器注入
- setter注入
- 接口注入
除了控制反轉(zhuǎn)可以實現(xiàn)依賴注入外,還有服務(wù)定位器模式也可以解除一定的依賴性。
一個簡單的例子
從這里開始翻譯:
In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly useful function is implemented by a single method.
在這個例子中凰狞,我正在寫一個提供由特定導(dǎo)演指導(dǎo)的電影列表組件,這個非常有用的功能是通過一個方法來實現(xiàn)的。
MovieLister.class
public 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()]);
}
}
這個函數(shù)的實現(xiàn)非常的幼稚端圈,它需要一個finder
對象(我們很快就知道)返回它的每一場電影,然后只是遍歷這個 list (allMovies
)子库,篩選出由特定導(dǎo)演指導(dǎo)的電影舱权。我不會去修補這個天真的代碼,因為這是本文真正意義上的基礎(chǔ)仑嗅。
這篇文章的真正要點就是這個finder
對象宴倍,特別的說,我們?nèi)绾螌?code>list對象和特定的finder
對象連接起來仓技。有趣的原因是鸵贬,我希望moviesDirectedBy
方法可以和所有電影如何存儲的方法完全獨立,所以在所有情況下浑彰,方法都可以引用finder
對象恭理,并且finder
對象也會知道如何返回findAll
方法,我可以定義一個finder
的接口來解決這個問題
public interface MovieFinder {
List findAll();
}
目前這些都很好的解耦郭变,但是在某些時候颜价,我需要想出一個具體的類來存放這些電影,在這種情況下诉濒,我把這些放到了我的lister構(gòu)造器中周伦。
MovieLister.class
public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}
總代碼如下:
MovieLister.class類
public class MovieLister{
private MovieFinder finder;
public MovieLister(){
// ColonDelimitedMovieFinder 冒號分割的文本 獲取movie列表
finder = new ColonDelimitedMovieFinder("movies1.txt");
}
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()]);
}
}
MovieFinder.class類
public interfave MovieFinder{
List findAll();
}
實現(xiàn)類的名稱來源于我從冒號分割的文本中獲取我的列表,我會告訴你多余細節(jié)未荒,畢竟只是一些實現(xiàn)专挪。
現(xiàn)在,如果只是我自己而使用這個類片排,一切都很好很正常寨腔。但是,當我的朋友非陈使眩渴望拷貝一份我的代碼的時候會發(fā)生什么事情呢迫卢?如果他們還把電影列表存儲在movies1.txt
中,那么一切都很美妙冶共。如果他們的存放電影文件的命名不同乾蛤,那么把文件名稱放到一個配置文件中每界,也很容易。但是家卖,如果他們有一個完全不同的形式存儲電影列表:SQL數(shù)據(jù)庫眨层,XML文件,web服務(wù)上荡,或者其他格式的文件呢趴樱?這種情況下,我們需要一個不同的類來獲取這些數(shù)據(jù)榛臼。因為現(xiàn)在我已經(jīng)定義了一個MovieFinder
接口伊佃,這不會改變我的moviesDirectedBy
方法,但是我仍然需要其他的方法沛善,來正確的實例化finder
接口

圖1: 在列表類中使用簡單創(chuàng)建的依賴關(guān)系
Figure 1 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?
In my book P of EAA, we described this situation as a Plugin. The implementation class for the finder isn't linked into the program at compile time, since I don't know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.
Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.
So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.
圖一顯示了這種狀況的依賴關(guān)系航揉,MovieLister
類同時依賴于MovieFinder
接口和實現(xiàn)。我們更喜歡如果MovieLister
類只依賴MovieFinder
的接口金刁,但是我們該怎么做呢帅涂?
在我的一本書中 P of EAA,我們把這種情況稱為一個Plugin尤蛮,finder
的實現(xiàn)類在編譯時沒有鏈接到程序中媳友,因為我不知道我的朋友們將使用什么。相反产捞,我希望我的lister
能夠和任何MovieFinder
實現(xiàn)類一起工作醇锚,并且MovieFinder
實現(xiàn)類將會自動的插入到程序中,而不是通過我來掌控坯临。問題是我應(yīng)該怎樣完成這些鏈接焊唬,以便我的lister
類對實現(xiàn)類是不知情的,但是它依然可以和finder
實例完成工作看靠。
把這些擴展成一個真正的系統(tǒng)赶促,或許我們需要很多這樣的服務(wù)和組件。在每一種情況下挟炬,我們可以通過一個接口(如果組件沒有設(shè)計接口的話鸥滨,可以使用組件),來讓我們的組件相互關(guān)聯(lián)谤祖。但是如果我們希望通過不同的方式部署這個系統(tǒng)婿滓,我們需要使用插件來處理這些服務(wù)的交互,以便我們可以在不同的部署中使用不同的實現(xiàn)粥喜。
所以核心問題是我們?nèi)绾螌⑦@些插件組裝到應(yīng)用文件之中空幻?這也是一些輕量級容器所面臨的主要問題之一,通常他們都是通過控制反轉(zhuǎn)來實現(xiàn)的容客。
控制反轉(zhuǎn)
當這些容器談到他們?nèi)绾斡杏蔑躅酰麄冋f是因為實現(xiàn)了“控制反轉(zhuǎn)”時候,我感到詫異缩挑〉剑控制反轉(zhuǎn)是架構(gòu)的一個典型特征,所以當這些輕量級框架說因使用了控制反轉(zhuǎn)而特殊供置,這就像說我的車是特殊的因為它有車輪谨湘。
問題是:“他們反轉(zhuǎn)的是哪些方面的控制?”芥丧,當我第一次遇到控制反轉(zhuǎn)的時候紧阔,是一個主要由用戶控制的界面。早期的用戶界面是被應(yīng)用程序所控制续担,你會有一系列如“輸入名稱”擅耽,”輸入地址”的命令;你的程序?qū)Ⅱ?qū)動提示并承載每一個響應(yīng)物遇。使用圖形(甚至基于屏幕的)UI乖仇,UI框架將會包含這個主循環(huán),并且询兴,你的程序?qū)峁┦录幚沓绦騺泶嫫聊簧系母鱾€區(qū)域乃沙。這個程序的主要控制權(quán)被顛倒了,從你轉(zhuǎn)移到了框架诗舰。
因此警儒,我認為我們需要一個更具體的名稱來表示這種模式,控制反轉(zhuǎn)(Inversion of Control)是一個過于籠統(tǒng)的術(shù)語眶根,因此人們感到困惑蜀铲。因此與很多IOC提倡者進行了大量討論之后,我們決定使用Dependency Injection(DI 依賴注入)這個詞汛闸。
我將開始討論各種形式的依賴注入蝙茶,但是我想指出的是,使用插件實現(xiàn)來去除依賴并不是應(yīng)用程序類的唯一方法诸老。你可以使用的另一種模式是服務(wù)定位器模式隆夯,在完成解釋依賴注入之后,我會討論這個問題别伏。
依賴注入的形式
依賴注入的基本思想是有一個分隔的對象蹄衷,一個裝配器,這個裝配器有適當?shù)?code>finder接口實現(xiàn)來填充lister
類中的字段厘肮,從而得到圖二的依賴關(guān)系圖

圖二:依賴注入的依賴關(guān)系
有三種主要方式的依賴注入愧口,我目前叫他們構(gòu)造函數(shù)注入,setter注入和接口注入类茂。如果你在目前關(guān)于依賴注入的討論中閱讀過耍属,你會聽到這些被稱之為type 1 IOC(接口注入)托嚣,type 2 IOC(setter注入),type 3 IOC(構(gòu)造器注入)的內(nèi)容厚骗。我發(fā)現(xiàn)數(shù)字名字很難記住示启,這就是為啥我在這兒使用了我曾經(jīng)叫的名字。
帶有PicoContainer的構(gòu)造器注入
我從展示如何使用輕量級框架picocontainer開始.我從這里開始的根本原因是我的幾位曾在ThoughtWorks重要的同事领舰,在PicoContainer 的開發(fā)中非常的活躍(沒錯夫嗓,這是一種企業(yè)裙帶關(guān)系)
PicoContainer使用構(gòu)造器來決定如何將finder
實現(xiàn)類注入到MoveiLister
類之中。為了這個工作冲秽,MovieLister
類需要聲明一個構(gòu)造器舍咖,它包含了它需要注入的所有東西。
MovieLister.class
public class MoveiLister{
private MovieFinder finder;
public MoveiLister(MovieFinder finder){
this.finder = finder;
}
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
本身也會被picocontainer
容器所管理锉桑,同理排霉,文本文件的文件名也會被注入到這個容器中
ColonMovieFinder.class
public class ColonMovieFinder{
private String filename;
public ColonMovieFinder(String filename){
this.filename = filename;
}
}
接下來picocontainer
容器需要被告知哪些實現(xiàn)類需要與接口相關(guān)聯(lián),以及哪些字符串需要被注入到MovieFinder
中
private MutablePicoContainer configureContainer() {
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams = {new ConstantParameter("movies1.txt")};
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation(MovieLister.class);
return pico;
}
這些配置代碼通常設(shè)置在一個不同的類中刨仑。在我們的例子中郑诺,每一個使用我的lister
類的朋友可能會在某些啟動類中編寫他們自己的配置代碼。當然杉武,在一個單獨的配置文件中編寫配置信息是很常見的辙诞。你可以寫一個類去讀取配置文件并恰當?shù)脑O(shè)置容器。雖然PicoContainer
本身不包含這個功能轻抱,但是有一個名為NanoContainer
的項目與之緊密相連飞涂,它提供了相關(guān)的包裝,允許你使用xml配置文件祈搜。這樣的一個納米級容器较店,將會解析xml,并配置一個潛在的PicoContainer
容燕,該項目的理念是將配置文件和底層機制分離梁呈。
要使用這個容器你可以這樣寫:
public void testWithPico() {
MutablePicoContainer pico = configureContainer();
MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
盡管在這個例子中,我使用了構(gòu)造器注入蘸秘,但是PicoContainer
同樣支持setter注入官卡,盡管它的開發(fā)者更喜歡構(gòu)造器注入。
setter注入和spring
spring框架是企業(yè)級Java開發(fā)的廣泛使用的框架醋虏,它包含事務(wù)抽象層寻咒,持久層框架,web應(yīng)用程序開發(fā)和JDBC颈嚼,像PicoContainer
框架一樣毛秘,它同樣支持構(gòu)造器注入和setter注入,但是它的開發(fā)人員跟傾向于setter注入,這使得它成為這個例子的合適選擇叫挟。
為了讓MovieLister
接受注入艰匙,我在該類中定義了set方法
MovieLister.class
public class MovieLister{
private MovieFinder finder;
public MoveiLister(MovieFinder finder){
this.finder = finder;
}
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
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()]);
}
}
同樣的,我也為filename
定義了一個set方法
public class ColonMovieFinder{
private String filename;
public ColonMovieFinder(String filename){
this.filename = filename;
}
public void setFilename(String filename){
this.filename = filename;
}
}
第三步是設(shè)置文件的配置霞揉,spring支持通過XML文件進行配置旬薯,也支持代碼配置,但XML文件是常見方式
<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>
測試文件應(yīng)該看起來是這樣子的:
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());
}
接口注入
第三種注入方式是接口注入适秩,Avalon是在某些情況下使用這種方式的框架的例子,稍后我會進一步討論硕舆,但是現(xiàn)在秽荞,我將使用一些簡單的示例代碼。