看了幾篇關于“依賴注入”的文章,無論是百度,還是 Wiki 等等,覺得原文說得是最清楚的~你可以下載我實現文中示例的 Demo~
這篇文章的另一個意義在于,如果你看不懂該文的英文,說明你應該補充文中的術語了,看看作者用什么詞來描述“控制反轉和依賴注入”~
下載 Demo 1(使用 PicoContainer 2.14.1) 下載 Demo 2(使用 PicoContainer 1.2 和 nanocontainer-1.1.2)In the Java community there's been a rush of lightweight containers that help to assemble components from different projects into a cohesive application. Underlying these containers is a common pattern to how they perform the wiring, a concept they refer under the very generic name of "Inversion of Control". In this article I dig into how this pattern works, under the more specific name of "Dependency Injection", and contrast it with the Service Locator alternative. The choice between them is less important than the principle of separating configuration from use.
Java 社群近來掀起了一陣輕量級容器的熱潮,這些容器能夠幫助開發者將來自不同項目的組件組裝成為一個內聚的應用程序。在這些容器的背后是同一個模式,決定了這些容器進行組件裝配的方式。人們稱這個模式為“控制反轉”( Inversion of Control,IoC)。本文我將深入探索這個模式的工作原理,給它一個更能描述其特點的名字——“依賴注入”(Dependency Injection),并將其與“服務定位器”(Service Locator)模式作一個比較。不過,它們之間的差異并不太重要,重要的是:將組件的配置與使用分離——而這都是兩個模式的目標。
One of the entertaining things about the enterprise Java world is the huge amount of activity in building alternatives to the mainstream J2EE technologies, much of it happening in open source. A lot of this is a reaction to the heavyweight complexity in the mainstream J2EE world, but much of it is also exploring alternatives and coming up with creative ideas. A common issue to deal with is how to wire together different elements: how do you fit together this web controller architecture with that database interface backing when they were built by different teams with little knowledge of each other.A number of frameworks have taken a stab at this problem, and several are branching out to provide a general capability to assemble components from different layers. These are often referred to as lightweight containers, examples include PicoContainer, and Spring.
在企業級 Java 的世界里存在一個有趣的現象:有很多人投入大量精力來研究主流 J2EE 技術的替代品——大都發生在 open source 社群。在很大程度上,這可以看作是開發者對主流 J2EE 技術的笨重和復雜作出的回應,但其中的確有很多極富創意的想法,確實提供了一些可供選擇的方案。J2EE 開發者常遇到的一個問題就是如何組裝不同的程序元素:如果 web 控制器體系結構和數據庫接口是由不同的團隊所開發的,彼此幾乎一無所知,你應該如何讓它們配合工作?很多框架嘗試過解決這個問題,有幾個框架索性朝這個方向發展,提供了更通用的“組裝各層組件”的方案。這樣的框架通常被稱為“輕量級容器”,包括 PicoContainer 和 Spring 等。
Underlying these containers are a number of interesting design principles, things that go beyond both these specific containers and indeed the Java platform. Here I want to start exploring some of these principles. The examples I use are in Java, but like most of my writing the principles are equally applicable to other OO environments, particularly .NET.
Component and Service(組件和服務)這些容器的背后是一些有趣的設計原則,這些原則已經超越了特定容器的范疇,甚至已經超越了Java 平臺。下面我就開始揭示這些原則。我使用的范例是 Java 代碼,但正如我的大多數文章一樣,這些原則也同樣適用于其他 OO 環境,特別是 .NET。
The topic of wiring elements together drags me almost immediately into the knotty terminology problems that surround the terms service and component. You find long and contradictory articles on the definition of these things with ease. For my purposes here are my current uses of these overloaded terms.
“裝配程序元素”的話題立即將我拖進了一個棘手的術語問題:如何區分“服務”(service)和“組件”(component)?你可以很容易地找到關于這兩個詞長篇大論、彼此矛盾的定義。鑒于此,對于這兩個遭到了嚴重濫用的詞,我將首先說明它們在本文中的用法。
I use component to mean a glob of software that's intended to be used, without change, by an application that is out of the control of the writers of the component. By 'without change' I mean that the using application doesn't change the source code of the components, although they may alter the component's behavior by extending it in ways allowed by the component writers.
所謂“組件”是指這樣一個軟件單元:它將被作者無法控制的其他應用程序使用,而后者不能對組件進行修改,也就是說,使用這個組件的應用程序不能修改組件的源代碼,但可以通過作者預留的某種途徑對其進行擴展以改變組件的行為。
A service is similar to a component in that it's used by foreign applications. The main difference is that I expect a component to be used locally (think jar file, assembly, dll, or a source import). A service will be used remotely through some remote interface, either synchronous or asynchronous (eg web service, messaging system, RPC, or socket.)
服務與組件相似,它們都將被外部的應用程序使用。兩者之間最大的差異在于:組件是在本地使用(例如 JAR 文件、程序集、DLL、或者源碼導入)。而服務是要通過——同步或異步的——遠程接口來遠程使用的(例如 Web Service、消息系統、RPC,或者 socket)。
I mostly use service in this article, but much of the same logic can be applied to local components too. Indeed often you need some kind of local component framework to easily access a remote service. But writing "component or service" is tiring to read and write, and services are much more fashionable at the moment.
A Naive Example(一個超級簡單的例子)本文我將主要使用“服務”這個詞,但文中的大多數邏輯也同樣適用于本地組件。事實上,為了方便地訪問遠程服務,你往往需要某種本地組件框架。不過,“組件或者服務”這樣一個詞組實在太麻煩了,而且“服務”這個詞當下也很流行,所以本文將用“服務”指代這兩者。
To help make all of this more concrete I'll use a running example to talk about all of this. Like all of my examples it's one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize what's going on without falling into the bog of a real example.
為了更好地說明問題,我會引入一個例子。與我之前使用的所有例子一樣,這是一個超級簡單的例子:它非常小,小到不夠真實,但足以幫助你看清其中的道理,而不至于陷入真實例子的泥潭。
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.
在這個例子中,我編寫一個組件,用于提供一份由特定導演執導的電影清單。實現這個功能只需要一個方法:
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()]);
}
The implementation of this function is naive in the extreme, it asks a finder object (which we'll get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This particular piece of naivety I'm not going to fix, since it's just the scaffolding for the real point of this article.
這個功能的實現極其簡單,moviesDirectedBy 方法首先請求 finder(影片搜尋者)對象(我們稍后會談到這個對象)以返回它所知道的所有影片,然后遍歷 finder 對象,并返回特定導演執導的影片。我只給出一個簡單的代碼片段。
The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I want my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.
我們真正想要考察的是 finder 對象,或者說,如何將 MovieLister 對象與特定的 finder 對象連接起來。為什么我們對這個問題特別感興趣?因為,我希望上面的 moviesDirectedBy 方法完全不依賴于影片的實際存儲方式。所以,所有的方法都會引用一個 finder 對象,而 finder 對象則必須知道如何回應 findAll 方法。為了幫助讀者更清楚地理解,我給 finder 定義了一個接口:
public interface MovieFinder { List findAll();
}
Now all of this is very well decoupled, but at some point I have to come up with a concrete class to actually come up with the movies. In this case I put the code for this in the constructor of my lister class.
現在,這兩個對象很好地解耦了。但當我要實際尋找影片時,就必須涉及到 MovieFinder 的某個具體子類。在這里,我把涉及具體子類的代碼放在 MovieLister 類構造函數中。
class MovieLister...
private MovieFinder finder;
public MovieLister() { finder = new ColonDelimitedMovieFinder("movies.txt"); }
The name of the implementation class comes from the fact that I'm getting my list from a colon delimited text file. I'll spare you the details, after all the point is just that there's some implementation.
ColonDelimitedMovieFinder 這個實現類的名字就說明,我將要從一個逗號分隔的文本文件中獲得影片列表。我不會給出該類的具體細節,畢竟這很簡單,你設想有這樣一個實現就行。
Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called "movies1.txt" then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an xml file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.
現在,如果這個類只是我自己使用,一切都沒有問題。但如果我的朋友嘆服于這個精彩的功能,也想使用我的程序,那又會怎樣呢?如果他們也想把影片清單保存在一個逗號分隔的文本文件中,并且也把這個文件命名為“movie.txt”,那么一切也沒問題。如果他們用完全不同的方式,例如 SQL 數據庫、XML 文件、Web Service,或是另一種格式的文本文件,來存儲影片清單呢?在這種情況下,我們需要用另一個類來獲取數據。由于已經定義了 MovieFinder 接口,我可以不用修改 moviesDirectedBy 方法。但我仍然需要通過某種途徑來獲得合適的 MovieFinder 接口的實例。
Figure 1: The dependencies using a simple creation in the lister class
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?
圖 1 顯示了這種情況下的依賴關系。MovieLister 類既依賴于 MovieFinder 接口,也依賴于該接口的具體的實現類。我們當然希望 MovieLister 類僅僅依賴于接口,但又如何獲得一個 MovieFinder 子類的實例呢?
也就是說,MovieLister 類依賴于 MovieFinder 接口,還要在該類中實例化該接口,但我們如何做才能不在 MovieLister 類內實例化 MovieFinder 接口,讓這個類僅僅依賴與 MovieFinder 接口?
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.
在 Patterns Of Enterprise Application Architecture 一書中,我們把這種情況稱為插件(Plugin)。MovieFinder 的實現類不是在編譯階段連入程序之中的,因為我并不知道我的朋友會使用哪個實現類。我們希望 MovieLister 類能夠與 MovieFinder 的任何實現類工作,并且允許在運行時插入具體的實現類,插入動作完全脫離我(原作者)的控制。問題是,如何設計這個連接過程,使 MovieLister 類在不知道實現類細節的前提下與其實例協同工作。
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.
Inversion of Control(控制反轉)所以,現在的核心問題是,如何將這些插件裝配到一個應用程序?這正是新生的輕量級容器所面臨的一個主要問題,而它們解決這個問題的手段無一例外地是控制反轉(Inversion Of Control)模式。
When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.
幾位輕量級容器的作者曾驕傲地對我說:這些容器非常有用,因為它們實現了控制反轉。這樣的說辭讓我深感迷惑:控制反轉是框架所共有的特征,如果僅僅因為使用了控制反轉就認為這些輕量級容器與眾不同,就好像在說我的轎車是與眾不同的,因為它有四個輪子。
The question is: "what aspect of control are they inverting?" When I first ran into inversion of control, it was in the main control of a user interface. Early user interfaces were controlled by the application program. You would have a sequence of commands like "Enter name", "enter address"; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.
問題的關鍵在于:它們反轉了哪方面的控制?我第一次接觸到的控制反轉是針對用戶界面的主控權。早期的用戶界面完全是由應用程序來控制的,你預先設計一系列命令,例如輸入姓名、輸入地址等,應用程序逐條輸出提示信息,并取回用戶的響應。而在圖形用戶界面環境下,UI 框架將負責執行一個主循環,你的應用程序只需為屏幕的各個區域提供事件處理函數即可。在這里,程序的控制權發生了反轉:從應用程序轉移到了框架。
For this new breed of containers the inversion is about how they lookup a plugin implementation. In my naive example the lister looked up the finder implementation by directly instantiating it. This stops the finder from being a plugin. The approach that these containers use is to ensure that any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.
對于這些新生的容器,它們反轉的是如何查找插件的具體實現。在前面的例子中,MovieLister 類直接實例化 MovieFinder。這樣,MovieFinder 也就不成其為插件了,因為它不是在運行時插入應用程序中的。而這些輕量級容器則使用了更為靈活的方法,只要插件遵循一定的規則,一個獨立的組件模塊就能夠將插件的具體實現注入到應用程序中。
As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.
因此,我想我們需要給這個模式起一個更能說明其特點的名字——“控制反轉”,這個名字太寬泛了,常常讓人有些迷惑。與多位 IoC 愛好者討論后,我們決定將這個模式叫做“依賴注入”(Dependency Injection)。
I'm going to start by talking about the various forms of dependency injection, but I'll point out now that that's not the only way of removing the dependency from the application class to the plugin implementation. The other pattern you can use to do this is Service Locator, and I'll discuss that after I'm done with explaining Dependency Injection.
Forms of Dependency Injection(依賴注入的幾種形式)下面,我將開始介紹 Dependency Injection 的幾種不同形式。不過,在此之前,我要先指出,要消除應用程序對插件實現的依賴,依賴注入并不是唯一的選擇,你也可以用 Service Locator 模式獲得同樣的效果。介紹完 Dependency Injection 模式之后,我會談到 Service Locator 模式。
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2.
Dependency Injection 模式的基本思想是,用一個單獨的對象(裝配器)來獲得 MovieFinder 的一個合適的實現,并將其實例賦給 MovieLister 類的一個字段。這樣,我們就得到了圖 2 所示的依賴圖。
Figure 2: The dependencies for a Dependency Injector
There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection. If you read about this stuff in the current discussions about Inversion of Control you'll hear these referred to as type 1 IoC (interface injection), type 2 IoC (setter injection) and type 3 IoC (constructor injection). I find numeric names rather hard to remember, which is why I've used the names I have here.
Constructor Injection with PicoContainer(用 PicoContainer 進行構造函數注入)依賴注入的形式主要有三種,我們分別將它們叫做構造函數注入(Constructor Injection)、設置方法注入(Setter Injection)和接口注入(Interface Injection)。如果讀過最近關于 IoC 的一些討論材料,你不難看出,這三種方法分別就是 type 1 IoC(接口注入)、type 2 IoC(設置方法注入)和 type 3 IoC(構造函數注入)。我發現數字編號往往比較難記,所以我使用了這里的命名方式。
I'll start with showing how this injection is done using a lightweight container called PicoContainer. I'm starting here primarily because several of my colleagues at ThoughtWorks are very active in the development of PicoContainer (yes, it's a sort of corporate nepotism.)
首先,我要展示如何用輕量級容器 PicoContainer 完成依賴注入。之所以從它開始,主要是因為我在 ThoughtWorks 公司的幾個同事在 PicoContainer 的開發社區中非常活躍(沒錯,也可以說是某種偏袒吧)。
PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.
PicoContainer 使用構造函數來決定如何將 MovieFinder 實例注入 MovieLister 類。因此,MovieLister 類必須聲明一個包含所有需要注入的元素的構造函數。
也就是說,所有需要注入的元素,是構造函數的形式參數。
class MovieLister...
public MovieLister(MovieFinder finder) { this.finder = finder;
}
The finder itself will also be managed by the pico container, and as such will have the filename of the text file injected into it by the container.
MovieFinder 實例本身也將由 PicoContainer 來管理,因此本文文件的名字也可以由容器注入:
class ColonMovieFinder...
public ColonMovieFinder(String filename) { this.filename = filename;
}
The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into the finder.
然后,需要告訴 pico 容器每個接口分別與哪個實現類相關聯,將什么字符串注入到 MovieFinder 組件。
private MutablePicoContainer configureContainer() { 新聞熱點
疑難解答