依赖注入学习总结
控制反轉(zhuǎn)
同義詞 依賴注入一般指控制反轉(zhuǎn)控制反轉(zhuǎn)(Inversion of Control,英文縮寫為IoC)是框架的重要特征,并非面向?qū)ο缶幊痰膶S眯g(shù)語。它與依賴注入(Dependency Injection,簡(jiǎn)稱DI)和依賴查找(Dependency Lookup)并沒有關(guān)系。
中文名 控制反轉(zhuǎn) 外文名 Inverse of Control 起源時(shí)間 1988年 目 ? ?的 描述框架的重要特征
目錄
1 起源
2 設(shè)計(jì)模式
3 優(yōu)缺點(diǎn)
4 實(shí)現(xiàn)初探
5 類型
6 實(shí)現(xiàn)策略
7 實(shí)現(xiàn)方式
起源
早在1988年,Ralph E. Johnson & Brian Foote在論文Designing Reusable Classes中寫到:
One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user's application code.
The framework often plays the role of the main program in coordinating and sequencing application activity.
This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.
《設(shè)計(jì)模式》至少兩次使用了控制反轉(zhuǎn),[1.6.7設(shè)計(jì)應(yīng)支持變化]和[5.10模板方法模式]。[1]?
2004年,Martin Fowler在其著名文章Inversion of Control Containers and the Dependency Injection pattern中[2] ?,使用了該術(shù)語。
但是,這些使用案例也使得IoC的含義變得含混。
設(shè)計(jì)模式
IoC可以認(rèn)為是一種全新的設(shè)計(jì)模式,但是理論和時(shí)間成熟相對(duì)較晚,并沒有包含在GoF中。
Interface Driven Design接口驅(qū)動(dòng),接口驅(qū)動(dòng)有很多好處,可以提供不同靈活的子類實(shí)現(xiàn),增加代碼穩(wěn)定和健壯性等等,但是接口一定是需要實(shí)現(xiàn)的,也就是如下語句遲早要執(zhí)行:AInterface a = new AInterfaceImp(); 這樣一來,耦合關(guān)系就產(chǎn)生了,如:
classA {AInterface a;A(){}AMethod()//一個(gè)方法{a = new AInterfaceImp();} }
Class A與AInterfaceImp就是依賴關(guān)系,如果想使用AInterface的另外一個(gè)實(shí)現(xiàn)就需要更改代碼了。當(dāng)然我們可以建立一個(gè)Factory來根據(jù)條件生成想要的AInterface的具體實(shí)現(xiàn),即:
InterfaceImplFactory {AInterface create(Object condition){if(condition == condA){return new AInterfaceImpA();}else if(condition == condB){return new AInterfaceImpB();}else{return new AInterfaceImp();}} }
表面上是在一定程度上緩解了以上問題,但實(shí)質(zhì)上這種代碼耦合并沒有改變。通過IoC模式可以徹底解決這種耦合,它把耦合從代碼中移出去,放到統(tǒng)一的XML 文件中,通過一個(gè)容器在需要的時(shí)候把這個(gè)依賴關(guān)系形成,即把需要的接口實(shí)現(xiàn)注入到需要它的類中,這可能就是“依賴注入”說法的來源了。
IoC模式,系統(tǒng)中通過引入實(shí)現(xiàn)了IoC模式的IoC容器,即可由IoC容器來管理對(duì)象的生命周期、依賴關(guān)系等,從而使得應(yīng)用程序的配置和依賴性規(guī)范與實(shí)際的應(yīng)用程序代碼分開。其中一個(gè)特點(diǎn)就是通過文本的配置文件進(jìn)行應(yīng)用程序組件間相互關(guān)系的配置,而不用重新修改并編譯具體的代碼。
當(dāng)前比較知名的IoC容器有:Pico Container、Avalon 、Spring、JBoss、HiveMind、EJB等。
在上面的幾個(gè)IoC容器中,輕量級(jí)的有Pico Container、Avalon、Spring、HiveMind等,超重量級(jí)的有EJB,而半輕半重的有容器有JBoss,Jdon等。
可以把IoC模式看做是工廠模式的升華,可以把IoC看作是一個(gè)大工廠,只不過這個(gè)大工廠里要生成的對(duì)象都是在XML文件中給出定義的,然后利用Java 的“反射”編程,根據(jù)XML中給出的類名生成相應(yīng)的對(duì)象。從實(shí)現(xiàn)來看,IoC是把以前在工廠方法里寫死的對(duì)象生成代碼,改變?yōu)橛蒟ML文件來定義,也就是把工廠和對(duì)象生成這兩者獨(dú)立分隔開來,目的就是提高靈活性和可維護(hù)性。
IoC中最基本的Java技術(shù)就是“反射”編程。反射又是一個(gè)生澀的名詞,通俗的說反射就是根據(jù)給出的類名(字符串)來生成對(duì)象。這種編程方式可以讓對(duì)象在生成時(shí)才決定要生成哪一種對(duì)象。反射的應(yīng)用是很廣泛的,像Hibernate、Spring中都是用“反射”做為最基本的技術(shù)手段。
在過去,反射編程方式相對(duì)于正常的對(duì)象生成方式要慢10幾倍,這也許也是當(dāng)時(shí)為什么反射技術(shù)沒有普遍應(yīng)用開來的原因。但經(jīng)SUN改良優(yōu)化后,反射方式生成對(duì)象和通常對(duì)象生成方式,速度已經(jīng)相差不大了(但依然有一倍以上的差距)。
優(yōu)缺點(diǎn)
IoC最大的好處是什么?因?yàn)榘褜?duì)象生成放在了XML里定義,所以當(dāng)我們需要換一個(gè)實(shí)現(xiàn)子類將會(huì)變成很簡(jiǎn)單(一般這樣的對(duì)象都是實(shí)現(xiàn)于某種接口的),只要修改XML就可以了,這樣我們甚至可以實(shí)現(xiàn)對(duì)象的熱插拔(有點(diǎn)像USB接口和SCSI硬盤了)。
IoC最大的缺點(diǎn)是什么?(1)生成一個(gè)對(duì)象的步驟變復(fù)雜了(事實(shí)上操作上還是挺簡(jiǎn)單的),對(duì)于不習(xí)慣這種方式的人,會(huì)覺得有些別扭和不直觀。(2)對(duì)象生成因?yàn)槭鞘褂梅瓷渚幊?#xff0c;在效率上有些損耗。但相對(duì)于IoC提高的維護(hù)性和靈活性來說,這點(diǎn)損耗是微不足道的,除非某對(duì)象的生成對(duì)效率要求特別高。(3)缺少IDE重構(gòu)操作的支持,如果在Eclipse要對(duì)類改名,那么你還需要去XML文件里手工去改了,這似乎是所有XML方式的缺陷所在。
實(shí)現(xiàn)初探
IOC關(guān)注服務(wù)(或應(yīng)用程序部件)是如何定義的以及他們應(yīng)該如何定位他們依賴的其它服務(wù)。通常,通過一個(gè)容器或定位框架來獲得定義和定位的分離,容器或定位框架負(fù)責(zé):
保存可用服務(wù)的集合
提供一種方式將各種部件與它們依賴的服務(wù)綁定在一起
為應(yīng)用程序代碼提供一種方式來請(qǐng)求已配置的對(duì)象(例如,一個(gè)所有依賴都滿足的對(duì)象), 這種方式可以確保該對(duì)象需要的所有相關(guān)的服務(wù)都可用。
類型
現(xiàn)有的框架實(shí)際上使用以下三種基本技術(shù)的框架執(zhí)行服務(wù)和部件間的綁定:
類型1 (基于接口): 可服務(wù)的對(duì)象需要實(shí)現(xiàn)一個(gè)專門的接口,該接口提供了一個(gè)對(duì)象,可以重用這個(gè)對(duì)象查找依賴(其它服務(wù))。早期的容器Excalibur使用這種模式。
類型2 (基于setter): 通過JavaBean的屬性(setter方法)為可服務(wù)對(duì)象指定服務(wù)。HiveMind和Spring采用這種方式。
類型3 (基于構(gòu)造函數(shù)): 通過構(gòu)造函數(shù)的參數(shù)為可服務(wù)對(duì)象指定服務(wù)。PicoContainer只使用這種方式。HiveMind和Spring也使用這種方式。
實(shí)現(xiàn)策略
IoC是一個(gè)很大的概念,可以用不同的方式實(shí)現(xiàn)。其主要形式有兩種:
◇依賴查找:容器提供回調(diào)接口和上下文條件給組件。EJB和Apache Avalon 都使用這種方式。這樣一來,組件就必須使用容器提供的API來查找資源和協(xié)作對(duì)象,僅有的控制反轉(zhuǎn)只體現(xiàn)在那些回調(diào)方法上(也就是上面所說的 類型1):容器將調(diào)用這些回調(diào)方法,從而讓應(yīng)用代碼獲得相關(guān)資源。
◇依賴注入:組件不做定位查詢,只提供普通的Java方法讓容器去決定依賴關(guān)系。容器全權(quán)負(fù)責(zé)的組件的裝配,它會(huì)把符合依賴關(guān)系的對(duì)象通過JavaBean屬性或者構(gòu)造函數(shù)傳遞給需要的對(duì)象。通過JavaBean屬性注射依賴關(guān)系的做法稱為設(shè)值方法注入(Setter Injection);將依賴關(guān)系作為構(gòu)造函數(shù)參數(shù)傳入的做法稱為構(gòu)造器注入(Constructor Injection)
實(shí)現(xiàn)方式
實(shí)現(xiàn)數(shù)據(jù)訪問層
數(shù)據(jù)訪問層有兩個(gè)目標(biāo)。第一是將數(shù)據(jù)庫引擎從應(yīng)用中抽象出來,這樣就可以隨時(shí)改變數(shù)據(jù)庫—比方說,從微軟SQL變成Oracle。不過在實(shí)踐上很少會(huì)這么做,也沒有足夠的理由未來使用實(shí)現(xiàn)數(shù)據(jù)訪問層而進(jìn)行重構(gòu)現(xiàn)有應(yīng)用的努力。[3]?
第二個(gè)目標(biāo)是將數(shù)據(jù)模型從數(shù)據(jù)庫實(shí)現(xiàn)中抽象出來。這使得數(shù)據(jù)庫或代碼開源根據(jù)需要改變,同時(shí)只會(huì)影響主應(yīng)用的一小部分——數(shù)據(jù)訪問層。這一目標(biāo)是值得的,為了在現(xiàn)有系統(tǒng)中實(shí)現(xiàn)它進(jìn)行必要的重構(gòu)。
模塊與接口重構(gòu)
依賴注入背后的一個(gè)核心思想是單一功能原則(single responsibility principle)。該原則指出,每一個(gè)對(duì)象應(yīng)該有一個(gè)特定的目的,而應(yīng)用需要利用這一目的的不同部分應(yīng)當(dāng)使用合適的對(duì)象。這意味著這些對(duì)象在系統(tǒng)的任何地方都可以重用。但在現(xiàn)有系統(tǒng)里面很多時(shí)候都不是這樣的。[3]?
隨時(shí)增加單元測(cè)試
把功能封裝到整個(gè)對(duì)象里面會(huì)導(dǎo)致自動(dòng)測(cè)試?yán)щy或者不可能。將模塊和接口與特定對(duì)象隔離,以這種方式重構(gòu)可以執(zhí)行更先進(jìn)的單元測(cè)試。按照后面再增加測(cè)試的想法繼續(xù)重構(gòu)模塊是誘惑力的,但這是錯(cuò)誤的。[3]?
使用服務(wù)定位器而不是構(gòu)造注入
實(shí)現(xiàn)控制反轉(zhuǎn)不止一種方法。最常見的辦法是使用構(gòu)造注入,這需要在對(duì)象首次被創(chuàng)建是提供所有的軟件依賴。然而,構(gòu)造注入要假設(shè)整個(gè)系統(tǒng)都使用這一模式,這意味著整個(gè)系統(tǒng)必須同時(shí)進(jìn)行重構(gòu)。這很困難、有風(fēng)險(xiǎn),且耗時(shí)。
========
依賴注入原理(為什么需要依賴注入)
http://blog.csdn.net/coderder/article/details/51897721目錄(?)[-]
0 前言
1 為什么需要依賴注入
2 依賴注入的實(shí)現(xiàn)方式
21 構(gòu)造函數(shù)注入Contructor Injection
22 setter注入
23 接口注入
3 最后
參考
0. 前言
在軟件工程領(lǐng)域,依賴注入(Dependency Injection)是用于實(shí)現(xiàn)控制反轉(zhuǎn)(Inversion of Control)的最常見的方式之一。本文主要介紹依賴注入原理和常見的實(shí)現(xiàn)方式,重點(diǎn)在于介紹這種年輕的設(shè)計(jì)模式的適用場(chǎng)景及優(yōu)勢(shì)。
1. 為什么需要依賴注入
控制反轉(zhuǎn)用于解耦,解的究竟是誰和誰的耦?這是我在最初了解依賴注入時(shí)候產(chǎn)生的第一個(gè)問題。
下面我引用Martin Flower在解釋介紹注入時(shí)使用的一部分代碼來說明這個(gè)問題。
public class MovieLister {private MovieFinder finder;public MovieLister() {finder = new MovieFinderImpl();}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()]);}... }public interface MovieFinder {List findAll(); }
我們創(chuàng)建了一個(gè)名為MovieLister的類來提供需要的電影列表,它moviesDirectedBy方法提供根據(jù)導(dǎo)演名來搜索電影的方式。真正負(fù)責(zé)搜索電影的是實(shí)現(xiàn)了MovieFinder接口的MovieFinderImpl,我們的MovieLister類在構(gòu)造函數(shù)中創(chuàng)建了一個(gè)MovieFinderImpl的對(duì)象。
目前看來,一切都不錯(cuò)。但是,當(dāng)我們希望修改finder,將finder替換為一種新的實(shí)現(xiàn)時(shí)(比如為MovieFinder增加一個(gè)參數(shù)表明Movie數(shù)據(jù)的來源是哪個(gè)數(shù)據(jù)庫),我們不僅需要修改MovieFinderImpl類,還需要修改我們MovieLister中創(chuàng)建MovieFinderImpl的代碼。
這就是依賴注入要處理的耦合。這種在MovieLister中創(chuàng)建MovieFinderImpl的方式,使得MovieLister不僅僅依賴于MovieFinder這個(gè)接口,它還依賴于MovieListImpl這個(gè)實(shí)現(xiàn)。 這種在一個(gè)類中直接創(chuàng)建另一個(gè)類的對(duì)象的代碼,和硬編碼(hard-coded strings)以及硬編碼的數(shù)字(magic numbers)一樣,是一種導(dǎo)致耦合的壞味道,我們可以把這種壞味道稱為硬初始化(hard init)。同時(shí),我們也應(yīng)該像記住硬編碼一樣記住,new(對(duì)象創(chuàng)建)是有毒的。
Hard Init帶來的主要壞處有兩個(gè)方面:1)上文所述的修改其實(shí)現(xiàn)時(shí),需要修改創(chuàng)建處的代碼;2)不便于測(cè)試,這種方式創(chuàng)建的類(上文中的MovieLister)無法單獨(dú)被測(cè)試,其行為和MovieFinderImpl緊緊耦合在一起,同時(shí),也會(huì)導(dǎo)致代碼的可讀性問題(“如果一段代碼不便于測(cè)試,那么它一定不便于閱讀。”)。
2. 依賴注入的實(shí)現(xiàn)方式
依賴注入其實(shí)并不神奇,我們?nèi)粘5拇a中很多都用到了依賴注入,但很少注意到它,也很少主動(dòng)使用依賴注入進(jìn)行解耦。這里我們簡(jiǎn)單介紹一下賴注入實(shí)現(xiàn)三種的方式。
2.1 構(gòu)造函數(shù)注入(Contructor Injection)
這是我認(rèn)為的最簡(jiǎn)單的依賴注入方式,我們修改一下上面代碼中MovieList的構(gòu)造函數(shù),使得MovieFinderImpl的實(shí)現(xiàn)在MovieLister類之外創(chuàng)建。這樣,MovieLister就只依賴于我們定義的MovieFinder接口,而不依賴于MovieFinder的實(shí)現(xiàn)了。
public class MovieLister {private MovieFinder finder;public MovieLister(MovieFinder finder) {this.finder = finder;}... }
2.2 setter注入
類似的,我們可以增加一個(gè)setter函數(shù)來傳入創(chuàng)建好的MovieFinder對(duì)象,這樣同樣可以避免在MovieFinder中hard init這個(gè)對(duì)象。
public class MovieLister {s...public void setFinder(MovieFinder finder) {this.finder = finder;} }
2.3 接口注入
接口注入使用接口來提供setter方法,其實(shí)現(xiàn)方式如下。
首先要?jiǎng)?chuàng)建一個(gè)注入使用的接口。
public interface InjectFinder {
? ? void injectFinder(MovieFinder finder);}
之后,我們讓MovieLister實(shí)現(xiàn)這個(gè)接口。
class MovieLister implements InjectFinder {
? ? ...
? ? public void injectFinder(MovieFinder finder) {
? ? ? this.finder = finder;
? ? }
? ? ...
}
========
淺談依賴注入
http://www.cnblogs.com/yangecnu/p/Introduce-Dependency-Injection.html最近幾天在看一本名為Dependency Injection in .NET 的書,主要講了什么是依賴注入,使用依賴注入的優(yōu)點(diǎn),以及.NET平臺(tái)上依賴注入的各種框架和用法。在這本書的開頭,講述了軟件工程中的一個(gè)重要的理念就是關(guān)注分離(Separation of concern, SoC)。依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開發(fā)出松散耦合(loose coupled)、可維護(hù)、可測(cè)試的代碼和程序。這條原則的做法是大家熟知的面向接口,或者說是面向抽象編程。
關(guān)于什么是依賴注入,在Stack Overflow上面有一個(gè)問題,如何向一個(gè)5歲的小孩解釋依賴注入,其中得分最高的一個(gè)答案是:
“When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired.
What you should be doing is stating a need, “I need something to drink with lunch,” and then we will make sure you have something when you sit down to eat.”
映射到面向?qū)ο蟪绦蜷_發(fā)中就是:高層類(5歲小孩)應(yīng)該依賴底層基礎(chǔ)設(shè)施(家長)來提供必要的服務(wù)。
編寫松耦合的代碼說起來很簡(jiǎn)單,但是實(shí)際上寫著寫著就變成了緊耦合。
使用例子來說明可能更簡(jiǎn)潔明了,首先來看看什么樣的代碼是緊耦合。
1 不好的實(shí)現(xiàn)
編寫松耦合代碼的第一步,可能大家都熟悉,那就是對(duì)系統(tǒng)分層。比如下面的經(jīng)典的三層架構(gòu)。
Classic 3-tier architecture
分完層和實(shí)現(xiàn)好是兩件事情,并不是說分好層之后就能夠松耦合了。
1.1 緊耦合的代碼
有很多種方式來設(shè)計(jì)一個(gè)靈活的,可維護(hù)的復(fù)雜應(yīng)用,但是n層架構(gòu)是一種大家比較熟悉的方式,這里面的挑戰(zhàn)在于如何正確的實(shí)現(xiàn)n層架構(gòu)。
假設(shè)要實(shí)現(xiàn)一個(gè)很簡(jiǎn)單的電子商務(wù)網(wǎng)站,要列出商品列表,如下:
product list page
下面就具體來演示通常的做法,是如何一步一步把代碼寫出緊耦合的。
1.1.1 數(shù)據(jù)訪問層
要實(shí)現(xiàn)商品列表這一功能,首先要編寫數(shù)據(jù)訪問層,需要設(shè)計(jì)數(shù)據(jù)庫及表,在SQLServer中設(shè)計(jì)的數(shù)據(jù)庫表Product結(jié)構(gòu)如下:
Product Table?
表設(shè)計(jì)好之后,就可以開始寫代碼了。在Visual Studio 中,新建一個(gè)名為DataAccessLayer的工程,添加一個(gè)ADO.NET Entity Data Model,此時(shí)Visual Studio的向?qū)?huì)自動(dòng)幫我們生成Product實(shí)體和ObjectContext DB操作上下文。這樣我們的 Data Access Layer就寫好了。
Product Entity Model
1.1.2 業(yè)務(wù)邏輯層
表現(xiàn)層實(shí)際上可以直接訪問數(shù)據(jù)訪問層,通過ObjectContext 獲取Product 列表。但是大多數(shù)情況下,我們不是直接把DB里面的數(shù)據(jù)展現(xiàn)出來,而是需要對(duì)數(shù)據(jù)進(jìn)行處理,比如對(duì)會(huì)員,需要對(duì)某些商品的價(jià)格打折。這樣我們就需要業(yè)務(wù)邏輯層,來處理這些與具體業(yè)務(wù)邏輯相關(guān)的事情。
新建一個(gè)類庫,命名為DomainLogic,然后添加一個(gè)名為ProductService的類:
public class ProductService {private readonly CommerceObjectContext objectContext;public ProductService(){this.objectContext = new CommerceObjectContext();}public IEnumerable<Product> GetFeaturedProducts(bool isCustomerPreferred){var discount = isCustomerPreferred ? .95m : 1;var products = (from p in this.objectContext.Productswhere p.IsFeaturedselect p).AsEnumerable();return from p in productsselect new Product{ProductId = p.ProductId,Name = p.Name,Description = p.Description,IsFeatured = p.IsFeatured,UnitPrice = p.UnitPrice * discount};} }
現(xiàn)在我們的業(yè)務(wù)邏輯層已經(jīng)實(shí)現(xiàn)了。
1.1.3 表現(xiàn)層
現(xiàn)在實(shí)現(xiàn)表現(xiàn)層邏輯,這里使用ASP.NET MVC,在Index 頁面的Controller中,獲取商品列表然后將數(shù)據(jù)返回給View。
public ViewResult Index()
{
? ? bool isPreferredCustomer =?
? ? ? ? this.User.IsInRole("PreferredCustomer");
? ? var service = new ProductService();
? ? var products =?
? ? ? ? service.GetFeaturedProducts(isPreferredCustomer);
? ? this.ViewData["Products"] = products;
? ? return this.View();
}
然后在View中將Controller中返回的數(shù)據(jù)展現(xiàn)出來:
<h2>Featured Products</h2>
<div>
<% var products =
? ? ? ? (IEnumerable<Product>)this.ViewData["Products"];
? ? foreach (var product in products)
? ? { %>
? ? <div>
? ? <%= this.Html.Encode(product.Name) %>
? ? (<%= this.Html.Encode(product.UnitPrice.ToString("C")) %>)
? ? </div>
<% } %>
</div>
1.2 分析
現(xiàn)在,按照三層“架構(gòu)”我們的代碼寫好了,并且也達(dá)到了要求。整個(gè)項(xiàng)目的結(jié)構(gòu)如下圖:
?Solution layout
這應(yīng)該是我們通常經(jīng)常寫的所謂的三層架構(gòu)。在Visual Studio中,三層之間的依賴可以通過項(xiàng)目引用表現(xiàn)出來。
1.2.1 依賴關(guān)系圖
現(xiàn)在我們來分析一下,這三層之間的依賴關(guān)系,很明顯,上面的實(shí)現(xiàn)中,DomianLogic需要依賴SqlDataAccess,因?yàn)镈omainLogic中用到了Product這一實(shí)體,而這個(gè)實(shí)體是定義在DataAccess這一層的。WebUI這一層需要依賴DomainLogic,因?yàn)镻roductService在這一層,同時(shí),還需要依賴DataAccess,因?yàn)樵赨I中也用到了Product實(shí)體,現(xiàn)在整個(gè)系統(tǒng)的依賴關(guān)系是這樣的:
Dependency graph in three-tier architecture
1.2.2 耦合性分析
使用三層結(jié)構(gòu)的主要目的是分離關(guān)注點(diǎn),當(dāng)然還有一個(gè)原因是可測(cè)試性。我們應(yīng)該將領(lǐng)域模型從數(shù)據(jù)訪問層和表現(xiàn)層中分離出來,這樣這兩個(gè)層的變化才不會(huì)污染領(lǐng)域模型。在大的系統(tǒng)中,這點(diǎn)很重要,這樣才能將系統(tǒng)中的不同部分隔離開來。
現(xiàn)在來看之前的實(shí)現(xiàn)中,有沒有模塊性,有沒有那個(gè)模塊可以隔離出來呢?,F(xiàn)在添加幾個(gè)新的case來看,系統(tǒng)是否能夠響應(yīng)這些需求:
添加新的用戶界面
除了WebForm用戶之外,可能還需要一個(gè)WinForm的界面,現(xiàn)在我們能否復(fù)用領(lǐng)域?qū)雍蛿?shù)據(jù)訪問層呢?從依賴圖中可以看到,沒有任何一個(gè)模塊會(huì)依賴表現(xiàn)層,因此很容易實(shí)現(xiàn)這一點(diǎn)變化。我們只需要?jiǎng)?chuàng)建一個(gè)WPF的富客戶端就可以。現(xiàn)在整個(gè)系統(tǒng)的依賴圖如下:
WPF client
更換新的數(shù)據(jù)源
可能過了一段時(shí)間,需要把整個(gè)系統(tǒng)部署到云上,要使用其他的數(shù)據(jù)存儲(chǔ)技術(shù),比如Azure Table Storage Service?,F(xiàn)在,整個(gè)訪問數(shù)據(jù)的協(xié)議發(fā)生了變化,訪問Azure Table Storage Service的方式是Http協(xié)議,而之前的大多數(shù).NET 訪問數(shù)據(jù)的方式都是基于ADO.NET 的方式。并且數(shù)據(jù)源的保存方式也發(fā)生了改變,之前是關(guān)系型數(shù)據(jù)庫,現(xiàn)在變成了key-value型數(shù)據(jù)庫。
Azure datatable?
由上面的依賴關(guān)系圖可以看出,所有的層都依賴了數(shù)據(jù)訪問層,如果修改數(shù)據(jù)訪問層,則領(lǐng)域邏輯層,和表現(xiàn)層都需要進(jìn)行相應(yīng)的修改。
1.2.3 問題
除了上面的各層之間耦合下過強(qiáng)之外,代碼中還有其他問題。
領(lǐng)域模型似乎都寫到了數(shù)據(jù)訪問層中。所以領(lǐng)域模型看起來依賴了數(shù)據(jù)訪問層。在數(shù)據(jù)訪問層中定義了名為Product的類,這種類應(yīng)該是屬于領(lǐng)域模型層的。
表現(xiàn)層中摻入了決定某個(gè)用戶是否是會(huì)員的邏輯。這種業(yè)務(wù)邏輯應(yīng)該是 業(yè)務(wù)邏輯層中應(yīng)該處理的,所以也應(yīng)該放到領(lǐng)域模型層
ProductService因?yàn)橐蕾嚵藬?shù)據(jù)訪問層,所以也會(huì)依賴在web.config 中配置的數(shù)據(jù)庫連接字符串等信息。這使得,整個(gè)業(yè)務(wù)邏輯層也需要依賴這些配置才能正常運(yùn)行。
在View中,包含了太多了函數(shù)性功能。他執(zhí)行了強(qiáng)制類型轉(zhuǎn)換,字符串格式化等操作,這些功能應(yīng)該是在界面顯示得模型中完成。
上面可能是我們大多數(shù)寫代碼時(shí)候的實(shí)現(xiàn), UI界面層去依賴了數(shù)據(jù)訪問層,有時(shí)候偷懶就直接引用了這一層,因?yàn)閷?shí)體定義在里面了。業(yè)務(wù)邏輯層也是依賴數(shù)據(jù)訪問層,直接在業(yè)務(wù)邏輯里面使用了數(shù)據(jù)訪問層里面的實(shí)體。這樣使得整個(gè)系統(tǒng)緊耦合,并且可測(cè)試性差。那現(xiàn)在我們看看,如何修改這樣一個(gè)系統(tǒng),使之達(dá)到松散耦合,從而提高可測(cè)試性呢?
2 較好的實(shí)現(xiàn)
依賴注入能夠較好的解決上面出現(xiàn)的問題,現(xiàn)在可以使用這一思想來重新實(shí)現(xiàn)前面的系統(tǒng)。之所以重新實(shí)現(xiàn)是因?yàn)?#xff0c;前面的實(shí)現(xiàn)在一開始的似乎就沒有考慮到擴(kuò)展性和松耦合,使用重構(gòu)的方式很難達(dá)到理想的效果。對(duì)于小的系統(tǒng)來說可能還可以,但是對(duì)于一個(gè)大型的系統(tǒng),應(yīng)該是比較困難的。
在寫代碼的時(shí)候,要管理好依賴性,在前面的實(shí)現(xiàn)這種,代碼直接控制了依賴性:當(dāng)ProductService需要一個(gè)ObjectContext類的似乎,直接new了一個(gè),當(dāng)HomeController需要一個(gè)ProductService的時(shí)候,直接new了一個(gè),這樣看起來很酷很方便,實(shí)際上使得整個(gè)系統(tǒng)具有很大的局限性,變得緊耦合。new 操作實(shí)際上就引入了依賴, 控制反轉(zhuǎn)這種思想就是要使的我們比較好的管理依賴。
2.1 松耦合的代碼
2.1.1 表現(xiàn)層
首先從表現(xiàn)層來分析,表現(xiàn)層主要是用來對(duì)數(shù)據(jù)進(jìn)行展現(xiàn),不應(yīng)該包含過多的邏輯。在Index的View頁面中,代碼希望可以寫成這樣
<h2>
? ? Featured Products</h2>
<div>
? ? <% foreach (var product in this.Model.Products)
? ? ? ? { %>
? ? <div>
? ? ? ? <%= this.Html.Encode(product.SummaryText) %></div>
? ? <% } %>
</div>
可以看出,跟之前的表現(xiàn)層代碼相比,要整潔很多。很明顯是不需要進(jìn)行類型轉(zhuǎn)換,要實(shí)現(xiàn)這樣的目的,只需要讓Index.aspx這個(gè)視圖繼承自 System.Web.Mvc.ViewPage<FeaturedProductsViewModel> 即可,當(dāng)我們?cè)趶腃ontroller創(chuàng)建View的時(shí)候,可以進(jìn)行選擇,然后會(huì)自動(dòng)生成。整個(gè)用于展示的信息放在了SummaryText字段中。
這里就引入了一個(gè)視圖模型(View-Specific Models),他封裝了視圖的行為,這些模型只是簡(jiǎn)單的POCOs對(duì)象(Plain Old CLR Objects)。FeatureProductsViewModel中包含了一個(gè)List列表,每個(gè)元素是一個(gè)ProductViewModel類,其中定義了一些簡(jiǎn)單的用于數(shù)據(jù)展示的字段。
FeatureProductsViewModel
現(xiàn)在在Controller中,我們只需要給View返回FeatureProductsViewModel對(duì)象即可。比如:
public ViewResult Index()
{
? ? var vm = new FeaturedProductsViewModel();
? ? return View(vm);
}
現(xiàn)在返回的是空列表,具體的填充方式在領(lǐng)域模型中,我們接著看領(lǐng)域模型層。
2.1.2 領(lǐng)域邏輯層
新建一個(gè)類庫,這里面包含POCOs和一些抽象類型。POCOs用來對(duì)領(lǐng)域建模,抽象類型提供抽象作為到達(dá)領(lǐng)域模型的入口。依賴注入的原則是面向接口而不是具體的類編程,使得我們可以替換具體實(shí)現(xiàn)。
現(xiàn)在我們需要為表現(xiàn)層提供數(shù)據(jù)。因此用戶界面層需要引用領(lǐng)域模型層。對(duì)數(shù)據(jù)訪問層的簡(jiǎn)單抽象可以采用Patterns of Enterprise Application Architecture一書中講到的Repository模式。因此定義一個(gè)ProductRepository抽象類,注意是抽象類,在領(lǐng)域模型庫中。它定義了一個(gè)獲取所有特價(jià)商品的抽象方法:
public abstract class ProductRepository
{
? ? public abstract IEnumerable<Product> GetFeaturedProducts();
}
這個(gè)方法的Product類中只定義了商品的基本信息比如名稱和單價(jià)。整個(gè)關(guān)系圖如下:
Domain model?
現(xiàn)在來看表現(xiàn)層,HomeController中的Index方法應(yīng)該要使用ProductService實(shí)例類來獲取商品列表,執(zhí)行價(jià)格打折,并且把Product類似轉(zhuǎn)化為ProductViewModel實(shí)例,并將該實(shí)例加入到FeaturesProductsViewModel中。因?yàn)镻roductService有一個(gè)帶有類型為ProductReposity抽象類的構(gòu)造函數(shù),所以這里可以通過構(gòu)造函數(shù)注入實(shí)現(xiàn)了ProductReposity抽象類的實(shí)例。這里和之前的最大區(qū)別是,我們沒有使用new關(guān)鍵字來立即new一個(gè)對(duì)象,而是通過構(gòu)造函數(shù)的方式傳入具體的實(shí)現(xiàn)。
現(xiàn)在來看表現(xiàn)層代碼:
public partial class HomeController : Controller
{
? ? private readonly ProductRepository repository;
? ? public HomeController(ProductRepository repository)
? ? {
? ? ? ? if (repository == null)
? ? ? ? {
? ? ? ? ? ? throw new ArgumentNullException("repository");
? ? ? ? }
? ? ? ? this.repository = repository;
? ? }
? ? public ViewResult Index()
? ? {
? ? ? ? var productService = new ProductService(this.repository);
? ? ? ? var vm = new FeaturedProductsViewModel();
? ? ? ? var products = productService.GetFeaturedProducts(this.User);
? ? ? ? foreach (var product in products)
? ? ? ? {
? ? ? ? ? ? var productVM = new ProductViewModel(product);
? ? ? ? ? ? vm.Products.Add(productVM);
? ? ? ? }
? ? ? ? return View(vm);
? ? }
}
在HomeController的構(gòu)造函數(shù)中,傳入了實(shí)現(xiàn)了ProductRepository抽象類的一個(gè)實(shí)例,然后將該實(shí)例保存在定義的私有的只讀的ProductRepository類型的repository對(duì)象中,這就是典型的通過構(gòu)造函數(shù)注入。在Index方法中,獲取數(shù)據(jù)的ProductService類中的主要功能,實(shí)際上是通過傳入的repository類來代理完成的。
ProductService類是一個(gè)純粹的領(lǐng)域?qū)ο?#xff0c;實(shí)現(xiàn)如下:
public class ProductService
{
? ? private readonly ProductRepository repository;
? ? public ProductService(ProductRepository repository)
? ? {
? ? ? ? if (repository == null)
? ? ? ? {
? ? ? ? ? ? throw new ArgumentNullException("repository");
? ? ? ? }
? ? ? ? this.repository = repository;
? ? }
? ? public IEnumerable<DiscountedProduct> GetFeaturedProducts(IPrincipal user)
? ? {
? ? ? ? if (user == null)
? ? ? ? {
? ? ? ? ? ? throw new ArgumentNullException("user");
? ? ? ? }
? ? ? ? return from p in
? ? ? ? ? ? ? ? ? ? ? ? this.repository.GetFeaturedProducts()
? ? ? ? ? ? ? ? select p.ApplyDiscountFor(user);
? ? }
}
可以看到ProductService也是通過構(gòu)造函數(shù)注入的方式,保存了實(shí)現(xiàn)了ProductReposity抽象類的實(shí)例,然后借助該實(shí)例中的GetFeatureProducts方法,獲取原始列表數(shù)據(jù),然后進(jìn)行打折處理,進(jìn)而實(shí)現(xiàn)了自己的GetFeaturedProducts方法。在該GetFeaturedProducts方法中,跟之前不同的地方在于,現(xiàn)在的參數(shù)是IPrincipal,而不是之前的bool型,因?yàn)榕袛嘤脩舻臓顩r,這是一個(gè)業(yè)務(wù)邏輯,不應(yīng)該在表現(xiàn)層處理。IPrincipal是BCL中的類型,所以不存在額外的依賴。我們應(yīng)該基于接口編程IPrincipal是應(yīng)用程序用戶的一種標(biāo)準(zhǔn)方式。
這里將IPrincipal作為參數(shù)傳遞給某個(gè)方法,然后再里面調(diào)用實(shí)現(xiàn)的方式是依賴注入中的方法注入的手段。和構(gòu)造函數(shù)注入一樣,同樣是將內(nèi)部實(shí)現(xiàn)代理給了傳入的依賴對(duì)象。
現(xiàn)在我們只剩下兩塊地方?jīng)]有處理了:
沒有ProductRepository的具體實(shí)現(xiàn),這個(gè)很容易實(shí)現(xiàn),后面放到數(shù)據(jù)訪問層里面去處理,我們只需要?jiǎng)?chuàng)建一個(gè)具體的實(shí)現(xiàn)了ProductRepository的數(shù)據(jù)訪問類即可。
默認(rèn)上,ASP.NET MVC 希望Controller對(duì)象有自己的默認(rèn)構(gòu)造函數(shù),因?yàn)槲覀冊(cè)贖omeController中添加了新的構(gòu)造函數(shù)來注入依賴,所以MVC框架不知道如何解決創(chuàng)建實(shí)例,因?yàn)橛幸蕾?。這個(gè)問題可以通過開發(fā)一個(gè)IControllerFactory來解決,該對(duì)象可以創(chuàng)建一個(gè)具體的ProductRepositry實(shí)例,然后傳給HomeController這里不多講。
現(xiàn)在我們的領(lǐng)域邏輯層已經(jīng)寫好了。在該層,我們只操作領(lǐng)域模型對(duì)象,以及.NET BCL 中的基本對(duì)象。模型使用POCOs來表示,命名為Product。領(lǐng)域模型層必須能夠和外界進(jìn)行交流(database),所以需要一個(gè)抽象類(Repository)來時(shí)完成這一功能,并且在必要的時(shí)候,可以替換具體實(shí)現(xiàn)。
2.1.3 數(shù)據(jù)訪問層
現(xiàn)在我們可以使用LINQ to Entity來實(shí)現(xiàn)具體的數(shù)據(jù)訪問層邏輯了。因?yàn)橐獙?shí)現(xiàn)領(lǐng)域模型的ProductRepository抽象類,所以需要引入領(lǐng)域模型層。注意,這里的依賴變成了數(shù)據(jù)訪問層依賴領(lǐng)域模型層。跟之前的恰好相反,代碼實(shí)現(xiàn)如下:
public class SqlProductRepository : Domain.ProductRepository
{
? ? private readonly CommerceObjectContext context;
? ? public SqlProductRepository(string connString)
? ? {
? ? ? ? this.context =
? ? ? ? ? ? new CommerceObjectContext(connString);
? ? }
? ? public override IEnumerable<Domain.Product> GetFeaturedProducts()
? ? {
? ? ? ? var products = (from p in this.context.Products
? ? ? ? ? ? ? ? ? ? ? ? where p.IsFeatured
? ? ? ? ? ? ? ? ? ? ? ? select p).AsEnumerable();
? ? ? ? return from p in products
? ? ? ? ? ? ? ? select p.ToDomainProduct();
? ? }
}
在這里需要注意的是,在領(lǐng)域模型層中,我們定義了一個(gè)名為Product的領(lǐng)域模型,然后再數(shù)據(jù)訪問層中Entity Framework幫我們也生成了一個(gè)名為Product的數(shù)據(jù)訪問層實(shí)體,他是和db中的Product表一一對(duì)應(yīng)的。所以我們?cè)诜椒ǚ祷氐臅r(shí)候,需要把類型從db中的Product轉(zhuǎn)換為領(lǐng)域模型中的POCOs Product對(duì)象。
two product class in the system?
Domain Model中的Product是一個(gè)POCOs類型的對(duì)象,他僅僅包含領(lǐng)域模型中需要用到的一些基本字段,DataAccess中的Product對(duì)象是映射到DB中的實(shí)體,它包含數(shù)據(jù)庫中Product表定義的所有字段,在數(shù)據(jù)表現(xiàn)層中我們 定義了一個(gè)ProductViewModel數(shù)據(jù)展現(xiàn)的Model。
這兩個(gè)對(duì)象之間的轉(zhuǎn)換很簡(jiǎn)單:
public class Product
{
? ? public Domain.Product ToDomainProduct()
? ? {
? ? ? ? Domain.Product p = new Domain.Product();
? ? ? ? p.Name = this.Name;
? ? ? ? p.UnitPrice = this.UnitPrice;
? ? ? ? return p;
? ? }
}
2.2 分析
2.2.1 依賴關(guān)系圖
現(xiàn)在,整個(gè)系統(tǒng)的依賴關(guān)系圖如下:
Dependency graph in DDD
表現(xiàn)層和數(shù)據(jù)訪問層都依賴領(lǐng)域模型層,這樣,在前面的case中,如果我們新添加一個(gè)UI界面;更換一種數(shù)據(jù)源的存儲(chǔ)和獲取方式,只需要修改對(duì)應(yīng)層的代碼即可,領(lǐng)域模型層保持了穩(wěn)定。
2.2.2 時(shí)序圖
整個(gè)系統(tǒng)的時(shí)序圖如下:
Sequence Diagram?
系統(tǒng)啟動(dòng)的時(shí)候,在Global.asax中創(chuàng)建了一個(gè)自定義了Controller工廠類,應(yīng)用程序?qū)⑵浔4嬖诒镜乇銉煞N,當(dāng)頁面請(qǐng)求進(jìn)來的時(shí)候,程序出發(fā)該工廠類的CreateController方法,并查找web.config中的數(shù)據(jù)庫連接字符串,將其傳遞給新的SqlProductRepository實(shí)例,然后將SqlProductRepository實(shí)例注入到HomeControll中,并返回。
然后應(yīng)用調(diào)用HomeController的實(shí)例方法Index來創(chuàng)建新的ProductService類,并通過構(gòu)造函數(shù)傳入SqlProductRepository。ProductService的GetFeaturedProducts 方法代理給SqlProductRepository實(shí)例去實(shí)現(xiàn)。
最后,返回填充好了FeaturedProductViewModel的ViewResult對(duì)象給頁面,然后MVC進(jìn)行合適的展現(xiàn)。
2.2.3 新的結(jié)構(gòu)
在1.1的實(shí)現(xiàn)中,采用了三層架構(gòu),在改進(jìn)后的實(shí)現(xiàn)中,在UI層和領(lǐng)域模型層中加入了一個(gè)表現(xiàn)模型(presentation model)層。如下圖:
presentation model layer
?
將Controllers和ViewModel從表現(xiàn)層移到了表現(xiàn)模型層,僅僅將視圖(.aspx和.ascx文件)和聚合根對(duì)象(Composition Root)保留在了表現(xiàn)層中。之所以這樣處理,是可以使得盡可能的使得表現(xiàn)層能夠可配置而其他部分盡可能的可以保持不變。
3. 結(jié)語
一不小心我們就編寫出了緊耦合的代碼,有時(shí)候以為分層了就可以解決這一問題,但是大多數(shù)的時(shí)候,都沒有正確的實(shí)現(xiàn)分層。之所以容易寫出緊耦合的代碼有一個(gè)原因是因?yàn)榫幊陶Z言或者開發(fā)環(huán)境允許我們只要需要一個(gè)新的實(shí)例對(duì)象,就可以使用new關(guān)鍵字來實(shí)例化一個(gè)。如果我們需要添加依賴,Visual Studio有些時(shí)候可以自動(dòng)幫我們添加引用。這使得我們很容易就犯錯(cuò),使用new關(guān)鍵字,就可能會(huì)引入以來;添加引用就會(huì)產(chǎn)生依賴。
減少new引入的依賴及緊耦合最好的方式是使用構(gòu)造函數(shù)注入依賴這種設(shè)計(jì)模式:即如果我們需要一個(gè)依賴的實(shí)例,通過構(gòu)造函數(shù)注入。在第二個(gè)部分的實(shí)現(xiàn)演示了如何針對(duì)抽象而不是具體編程。
構(gòu)造函數(shù)注入是反轉(zhuǎn)控制的一個(gè)例子,因?yàn)槲覀兎崔D(zhuǎn)了對(duì)依賴的控制。不是使用new關(guān)鍵字創(chuàng)建一個(gè)實(shí)例,而是將這種行為委托給了第三方實(shí)現(xiàn)。
希望本文能夠給大家了解如何真正實(shí)現(xiàn)三層架構(gòu),編寫松散耦合,可維護(hù),可測(cè)試性的代碼提供一些幫助。
========
理解依賴注入(IOC)和學(xué)習(xí)Unity
http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.htmlIOC:英文全稱:Inversion of Control,中文名稱:控制反轉(zhuǎn),它還有個(gè)名字叫依賴注入(Dependency Injection)。
作用:將各層的對(duì)象以松耦合的方式組織在一起,解耦,各層對(duì)象的調(diào)用完全面向接口。當(dāng)系統(tǒng)重構(gòu)的時(shí)候,代碼的改寫量將大大減少。
理解依賴注入:
? ? 當(dāng)一個(gè)類的實(shí)例需要另一個(gè)類的實(shí)例協(xié)助時(shí),在傳統(tǒng)的程序設(shè)計(jì)過程中,通常有調(diào)用者來創(chuàng)建被調(diào)用者的實(shí)例。然而采用依賴注入的方式,創(chuàng)建被調(diào)用者的工作不再由調(diào)用者來完成,因此叫控制反轉(zhuǎn),創(chuàng)建被調(diào)用者的實(shí)例的工作由IOC容器來完成,然后注入調(diào)用者,因此也稱為依賴注入。
舉個(gè)有意思的例子(來源于互聯(lián)網(wǎng))
假如我們要設(shè)計(jì)一個(gè)Girl和一個(gè)Boy類,其中Girl有Kiss方法,即Girl想要Kiss一個(gè)Boy,首先問題是Girl如何認(rèn)識(shí)Boy?
? ? 在我們中國常見的MM認(rèn)識(shí)GG的方式有以下幾種:
? ? A 青梅竹馬 ? ?B 親友介紹 ? C 父母包辦
? ? 哪一種是最好的?
1.青梅竹馬:很久很久以前,有個(gè)有錢的地主家的一閨女叫Lily,她老爸把她許配給縣太爺?shù)膬鹤覬immy,屬于指腹為婚,Lily非常喜歡kiss,但是只能kiss Jimmy
public class Lily{ ?
? ? ? ? public Jimmy jimmy; ??
? ? ? ? public Girl() ?
? ? ? ? { ?
? ? ? ? ? ? jimmy=new Jimmy(); ?
? ? ? ? } ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? jimmy.Kiss(); ?
? ? ? ? } ?
? ? } ?
??
? ? public class Jimmy ?
? ? { ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? Console.WriteLine("kissing"); ?
? ? ? ? } ?
? ? } ?
這樣導(dǎo)致Lily對(duì)Jimmy的依賴性非常強(qiáng),緊耦合。
2.親友介紹:經(jīng)常Kiss同一個(gè)人令Lily有些厭惡了,她想嘗試新人,于是與Jimmy分手了,通過親朋好友(中間人)來介紹
public class Lily{ ?
? ? ? ? public Boy boy; ??
??
? ? ? ? public Girl() ?
? ? ? ? { ?
? ? ? ? ? ? boy=BoyFactory.createBoy(); ?
? ? ? ? } ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? boy.Kiss(); ?
? ? ? ? } ?
? ? } ?
親友介紹,固然是好。如果不滿意,盡管另外換一個(gè)好了。但是,親友BoyFactory經(jīng)常是以Singleton的形式出現(xiàn),不然就是,存在于Globals,無處不在,無處不能。實(shí)在是太繁瑣了一點(diǎn),不夠靈活。我為什么一定要這個(gè)親友摻和進(jìn)來呢?為什么一定要付給她介紹費(fèi)呢?萬一最好的朋友愛上了我的男朋友呢?
?
3.父母包辦:一切交給父母,自己不用非吹灰之力,Lily在家只Kiss
public class Lily{ ?
? ? ? ? public Boy boy; ??
? ? ? ? public Girl(Boy boy) ?
? ? ? ? { ?
? ? ? ? ? ? this.boy=boy; ?
? ? ? ? } ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? this.boy.Kiss(); ?
? ? ? ? } ?
? ? } ?
?
?
Well,這是對(duì)Girl最好的方法,只要想辦法賄賂了Girl的父母,并把Boy交給他。那么我們就可以輕松的和Girl來Kiss了??磥韼浊陚鹘y(tǒng)的父母之命還真是有用哦。至少Boy和Girl不用自己瞎忙乎了。這就是IOC,將對(duì)象的創(chuàng)建和獲取提取到外部。由外部容器提供需要的組件。
在設(shè)計(jì)模式中我們應(yīng)該還知道依賴倒轉(zhuǎn)原則,應(yīng)是面向接口編程而不是面向功能實(shí)現(xiàn),好處是:多實(shí)現(xiàn)可以任意切換,我們的Boy應(yīng)該是實(shí)現(xiàn)Kissable接口。這樣一旦Girl不想kiss可惡的Boy的話,還可以kiss可愛的kitten和慈祥的grandmother
好在.net中微軟有一個(gè)輕量級(jí)的IoC框架Unity,支持構(gòu)造器注入,屬性注入,方法注入如下圖所示
具體使用方法如下圖所示
using System; ?
??
using Microsoft.Practices.Unity; ?
??
??
namespace ConsoleApplication9 ?
{ ?
? ? class Program ?
? ? { ?
? ? ? ? static void Main(string[] args) ?
? ? ? ? { ?
? ? ? ? ? ? //創(chuàng)建容器 ?
? ? ? ? ? ? IUnityContainer container=new UnityContainer(); ?
? ? ? ? ? ? //注冊(cè)映射 ?
? ? ? ? ? ? container.RegisterType<IKiss, Boy>(); ?
? ? ? ? ? ? //得到Boy的實(shí)例 ?
? ? ? ? ? ? var boy = container.Resolve<IKiss>(); ?
? ? ? ? ? ? ?
? ? ? ? ? ? Lily lily = new Lily(boy); ?
? ? ? ? ? ? lily.kiss(); ?
? ? ? ? } ?
? ? } ?
??
??
? ? public interface IKiss ?
? ? { ?
? ? ? ? void kiss(); ?
? ? } ?
? ? ??
??
? ? public class Lily:IKiss ?
? ? { ?
??
? ? ? ? public IKiss boy; ??
??
? ? ? ? public Lily(IKiss boy) ?
? ? ? ? { ?
? ? ? ? ? ? this.boy=boy; ?
? ? ? ? } ?
? ? ? ? public void kiss() ?
? ? ? ? { ?
? ? ? ? ? ? boy.kiss(); ?
? ? ? ? ? ? Console.WriteLine("lily kissing"); ?
? ? ? ? } ?
? ? } ?
??
? ? public class Boy : IKiss ?
? ? { ?
? ? ? ? public void kiss() ?
? ? ? ? { ?
? ? ? ? ? ? Console.WriteLine("boy kissing"); ?
? ? ? ? } ?
? ? } ?
} ?
如果采用配置文件注冊(cè)的話
<?xml version="1.0" encoding="utf-8" ?> ?
<configuration> ?
? <configSections> ?
? ? <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> ?
? </configSections> ?
? <unity> ?
? ? <containers> ?
? ? ? <container name="defaultContainer"> ?
? ? ? ? <register type="命名空間.接口類型1,命名空間" mapTo="命名空間.實(shí)現(xiàn)類型1,命名空間" /> ?
? ? ? ? <register type="命名空間.接口類型2,命名空間" mapTo="命名空間.實(shí)現(xiàn)類型2,命名空間" /> ?
? ? ? </container> ?
? ? </containers> ?
? </unity> ?
</configuration> ?
配置的后臺(tái)代碼:
UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) ?
? ? ? ? ? ? as UnityConfigurationSection; ?
configuration.Configure(container, "defaultContainer"); ?
可以通過方法ResolveAll來得到所有注冊(cè)對(duì)象的實(shí)例:
var Instances = container.Resolve<IKiss>();
Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴注入劃分為三種形式,即構(gòu)造器注入、屬性(設(shè)置)注入和接口注入,習(xí)慣將其劃分為一種(類型)匹配和三種注入:
類型匹配(Type Matching):雖然我們通過接口(或者抽象類)來進(jìn)行服務(wù)調(diào)用,但是服務(wù)本身還是實(shí)現(xiàn)在某個(gè)具體的服務(wù)類型中,這就需要某個(gè)類型注冊(cè)機(jī)制來解決服務(wù)接口和服務(wù)類型之間的匹配關(guān)系;
構(gòu)造器注入(Constructor Injection):IoC容器會(huì)智能地選擇選擇和調(diào)用適合的構(gòu)造函數(shù)以創(chuàng)建依賴的對(duì)象。如果被選擇的構(gòu)造函數(shù)具有相應(yīng)的參數(shù),IoC容器在調(diào)用構(gòu)造函數(shù)之前解析注冊(cè)的依賴關(guān)系并自行獲得相應(yīng)參數(shù)對(duì)象;
屬性注入(Property Injection):如果需要使用到被依賴對(duì)象的某個(gè)屬性,在被依賴對(duì)象被創(chuàng)建之后,IoC容器會(huì)自動(dòng)初始化該屬性;
方法注入(Method Injection):如果被依賴對(duì)象需要調(diào)用某個(gè)方法進(jìn)行相應(yīng)的初始化,在該對(duì)象創(chuàng)建之后,IoC容器會(huì)自動(dòng)調(diào)用該方法。
?
?
我們創(chuàng)建一個(gè)控制臺(tái)程序,定義如下幾個(gè)接口(IA、IB、IC和ID)和它們各自的實(shí)現(xiàn)類(A、B、C、D)。在類型A中定義了3個(gè)屬性B、C和D,其類型分別為接口IB、IC和ID。其中屬性B在構(gòu)在函數(shù)中被初始化,以為著它會(huì)以構(gòu)造器注入的方式被初始化;屬性C上應(yīng)用了DependencyAttribute特性,意味著這是一個(gè)需要以屬性注入方式被初始化的依賴屬性;屬性D則通過方法Initialize初始化,該方法上應(yīng)用了特性InjectionMethodAttribute,意味著這是一個(gè)注入方法在A對(duì)象被IoC容器創(chuàng)建的時(shí)候會(huì)被自動(dòng)調(diào)用。
public interface IA { } ?
? ? public interface IB { } ?
? ? public interface IC { } ?
? ? public interface ID { } ?
??
? ? public class A : IA ?
? ? { ?
? ? ? ? public IB B { get; set; } ?
? ? ? ? [Dependency] ?
? ? ? ? public IC C { get; set; } ?
? ? ? ? public ID D { get; set; } ?
??
? ? ? ? public A(IB b) ?
? ? ? ? { ?
? ? ? ? ? ? this.B = b; ?
? ? ? ? } ?
? ? ? ? [InjectionMethod] ?
? ? ? ? public void Initalize(ID d) ?
? ? ? ? { ?
? ? ? ? ? ? this.D = d; ?
? ? ? ? } ?
? ? } ?
? ? public class B : IB { } ?
? ? public class C : IC { } ?
? ? public class D : ID { } ?
然后我們?yōu)樵搼?yīng)用添加一個(gè)配置文件,并定義如下一段關(guān)于Unity的配置。這段配置定義了一個(gè)名稱為defaultContainer的Unity容器,并在其中完成了上面定義的接口和對(duì)應(yīng)實(shí)現(xiàn)類之間映射的類型匹配。
<?xml version="1.0" encoding="utf-8" ?> ?
<configuration> ?
? <configSections> ?
? ? <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> ?
? </configSections> ?
? <unity> ?
? ? <containers> ?
? ? ? <container name="defaultContainer"> ?
? ? ? ? <register type="UnityDemo.IA,UnityDemo" mapTo="UnityDemo.A, UnityDemo"/> ?
? ? ? ? <register type="UnityDemo.IB,UnityDemo" mapTo="UnityDemo.B, UnityDemo"/> ?
? ? ? ? <register type="UnityDemo.IC,UnityDemo" mapTo="UnityDemo.C, UnityDemo"/> ?
? ? ? ? <register type="UnityDemo.ID,UnityDemo" mapTo="UnityDemo.D, UnityDemo"/> ?
? ? ? </container> ?
? ? </containers> ?
? </unity> ?
</configuration> ?
最后在Main方法中創(chuàng)建一個(gè)代表IoC容器的UnityContainer對(duì)象,并加載配置信息對(duì)其進(jìn)行初始化。然后調(diào)用它的泛型的Resolve方法創(chuàng)建一個(gè)實(shí)現(xiàn)了泛型接口IA的對(duì)象。最后將返回對(duì)象轉(zhuǎn)變成類型A,并檢驗(yàn)其B、C和D屬性是否是空
class Program ?
? ? { ?
? ? ? ? static void Main(string[] args) ?
? ? ? ? { ?
? ? ? ? ? ? UnityContainer container = new UnityContainer(); ?
? ? ? ? ? ? UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection; ?
? ? ? ? ? ? configuration.Configure(container, "defaultContainer"); ?
? ? ? ? ? ? A a = container.Resolve<IA>() as A; ?
? ? ? ? ? ? if (null!=a) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? Console.WriteLine("a.B==null?{0}",a.B==null?"Yes":"No"); ?
? ? ? ? ? ? ? ? Console.WriteLine("a.C==null?{0}", a.C == null ? "Yes" : "No"); ?
? ? ? ? ? ? ? ? Console.WriteLine("a.D==null?{0}", a.D == null ? "Yes" : "No"); ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? } ?
從如下給出的執(zhí)行結(jié)果我們可以得到這樣的結(jié)論:通過Resolve<IA>方法返回的是一個(gè)類型為A的對(duì)象,該對(duì)象的三個(gè)屬性被進(jìn)行了有效的初始化。這個(gè)簡(jiǎn)單的程序分別體現(xiàn)了接口注入(通過相應(yīng)的接口根據(jù)配置解析出相應(yīng)的實(shí)現(xiàn)類型)、構(gòu)造器注入(屬性B)、屬性注入(屬性C)和方法注入(屬性D)
? a.B == null ? No
?a.C == null ? No
?a.D == null ? No
========
spring四種依賴注入方式
http://kb.cnblogs.com/page/45266/4/平常的java開發(fā)中,程序員在某個(gè)類中需要依賴其它類的方法,則通常是new一個(gè)依賴類再調(diào)用類實(shí)例的方法,這種開發(fā)存在的問題是new的類實(shí)例不好統(tǒng)一管理,spring提出了依賴注入的思想,即依賴類不由程序員實(shí)例化,而是通過spring容器幫我們new指定實(shí)例并且將實(shí)例注入到需要該對(duì)象的類中。依賴注入的另一種說法是“控制反轉(zhuǎn)”,通俗的理解是:平常我們new一個(gè)實(shí)例,這個(gè)實(shí)例的控制權(quán)是我們程序員,而控制反轉(zhuǎn)是指new實(shí)例工作不由我們程序員來做而是交給spring容器來做。
spring有多種依賴注入的形式,下面僅介紹spring通過xml進(jìn)行IOC配置的方式:
Set注入
這是最簡(jiǎn)單的注入方式,假設(shè)有一個(gè)SpringAction,類中需要實(shí)例化一個(gè)SpringDao對(duì)象,那么就可以定義一個(gè)private的SpringDao成員變量,然后創(chuàng)建SpringDao的set方法(這是ioc的注入入口):
Java代碼 ?收藏代碼
package com.bless.springdemo.action; ?
public class SpringAction { ?
? ? ? ? //注入對(duì)象springDao ?
? ? private SpringDao springDao; ?
? ? ? ? //一定要寫被注入對(duì)象的set方法 ?
? ? ? ? public void setSpringDao(SpringDao springDao) { ?
? ? ? ? this.springDao = springDao; ?
? ? } ?
??
? ? ? ? public void ok(){ ?
? ? ? ? springDao.ok(); ?
? ? } ?
} ?
隨后編寫spring的xml文件,<bean>中的name屬性是class屬性的一個(gè)別名,class屬性指類的全名,因?yàn)樵赟pringAction中有一個(gè)公共屬性Springdao,所以要在<bean>標(biāo)簽中創(chuàng)建一個(gè)<property>標(biāo)簽指定SpringDao。<property>標(biāo)簽中的name就是SpringAction類中的SpringDao屬性名,ref指下面<bean name="springDao"...>,這樣其實(shí)是spring將SpringDaoImpl對(duì)象實(shí)例化并且調(diào)用SpringAction的setSpringDao方法將SpringDao注入:
Java代碼 ?收藏代碼
<!--配置bean,配置后該類由spring管理--> ?
? ? <bean name="springAction" class="com.bless.springdemo.action.SpringAction"> ?
? ? ? ? <!--(1)依賴注入,配置當(dāng)前類中相應(yīng)的屬性--> ?
? ? ? ? <property name="springDao" ref="springDao"></property> ?
? ? </bean> ?
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean> ?
??
構(gòu)造器注入
這種方式的注入是指帶有參數(shù)的構(gòu)造函數(shù)注入,看下面的例子,我創(chuàng)建了兩個(gè)成員變量SpringDao和User,但是并未設(shè)置對(duì)象的set方法,所以就不能支持第一種注入方式,這里的注入方式是在SpringAction的構(gòu)造函數(shù)中注入,也就是說在創(chuàng)建SpringAction對(duì)象時(shí)要將SpringDao和User兩個(gè)參數(shù)值傳進(jìn)來:
Java代碼 ?收藏代碼
public class SpringAction { ?
? ? //注入對(duì)象springDao ?
? ? private SpringDao springDao; ?
? ? private User user; ?
? ? ??
? ? public SpringAction(SpringDao springDao,User user){ ?
? ? ? ? this.springDao = springDao; ?
? ? ? ? this.user = user; ?
? ? ? ? System.out.println("構(gòu)造方法調(diào)用springDao和user"); ?
? ? } ?
? ? ? ? ??
? ? ? ? public void save(){ ?
? ? ? ? user.setName("卡卡"); ?
? ? ? ? springDao.save(user); ?
? ? } ?
} ?
?
在XML文件中同樣不用<property>的形式,而是使用<constructor-arg>標(biāo)簽,ref屬性同樣指向其它<bean>標(biāo)簽的name屬性:
Xml代碼 ?收藏代碼
<!--配置bean,配置后該類由spring管理--> ?
? ? <bean name="springAction" class="com.bless.springdemo.action.SpringAction"> ?
? ? ? ? <!--(2)創(chuàng)建構(gòu)造器注入,如果主類有帶參的構(gòu)造方法則需添加此配置--> ?
? ? ? ? <constructor-arg ref="springDao"></constructor-arg> ?
? ? ? ? <constructor-arg ref="user"></constructor-arg> ?
? ? </bean> ?
? ? ? ? <bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean> ?
? ? ? ? ?<bean name="user" class="com.bless.springdemo.vo.User"></bean> ?
? 解決構(gòu)造方法參數(shù)的不確定性,你可能會(huì)遇到構(gòu)造方法傳入的兩參數(shù)都是同類型的,為了分清哪個(gè)該賦對(duì)應(yīng)值,則需要進(jìn)行一些小處理:
下面是設(shè)置index,就是參數(shù)位置:
Xml代碼 ?收藏代碼
<bean name="springAction" class="com.bless.springdemo.action.SpringAction"> ?
? ? ? ? <constructor-arg index="0" ref="springDao"></constructor-arg> ?
? ? ? ? <constructor-arg index="1" ref="user"></constructor-arg> ?
? ? </bean> ?
? 另一種是設(shè)置參數(shù)類型:
Xml代碼 ?收藏代碼
<constructor-arg type="java.lang.String" ref=""/> ?
?
靜態(tài)工廠的方法注入
靜態(tài)工廠顧名思義,就是通過調(diào)用靜態(tài)工廠的方法來獲取自己需要的對(duì)象,為了讓spring管理所有對(duì)象,我們不能直接通過"工程類.靜態(tài)方法()"來獲取對(duì)象,而是依然通過spring注入的形式獲取:
Java代碼 ?收藏代碼
package com.bless.springdemo.factory; ?
??
import com.bless.springdemo.dao.FactoryDao; ?
import com.bless.springdemo.dao.impl.FactoryDaoImpl; ?
import com.bless.springdemo.dao.impl.StaticFacotryDaoImpl; ?
??
public class DaoFactory { ?
? ? //靜態(tài)工廠 ?
? ? public static final FactoryDao getStaticFactoryDaoImpl(){ ?
? ? ? ? return new StaticFacotryDaoImpl(); ?
? ? } ?
} ?
同樣看關(guān)鍵類,這里我需要注入一個(gè)FactoryDao對(duì)象,這里看起來跟第一種注入一模一樣,但是看隨后的xml會(huì)發(fā)現(xiàn)有很大差別:
Java代碼 ?收藏代碼
?public class SpringAction { ?
? ? ? ? //注入對(duì)象 ?
? ? private FactoryDao staticFactoryDao; ?
? ? ??
? ? public void staticFactoryOk(){ ?
? ? ? ? staticFactoryDao.saveFactory(); ?
? ? } ?
? ? //注入對(duì)象的set方法 ?
? ? public void setStaticFactoryDao(FactoryDao staticFactoryDao) { ?
? ? ? ? this.staticFactoryDao = staticFactoryDao; ?
? ? } ?
} ?
?
Spring的IOC配置文件,注意看<bean name="staticFactoryDao">指向的class并不是FactoryDao的實(shí)現(xiàn)類,而是指向靜態(tài)工廠DaoFactory,并且配置 factory-method="getStaticFactoryDaoImpl"指定調(diào)用哪個(gè)工廠方法:
Xml代碼 ?收藏代碼
<!--配置bean,配置后該類由spring管理--> ?
? ? <bean name="springAction" class="com.bless.springdemo.action.SpringAction" > ?
? ? ? ? <!--(3)使用靜態(tài)工廠的方法注入對(duì)象,對(duì)應(yīng)下面的配置文件(3)--> ?
? ? ? ? <property name="staticFactoryDao" ref="staticFactoryDao"></property> ?
? ? ? ? ? ? ? ? </property> ?
? ? </bean> ?
? ? <!--(3)此處獲取對(duì)象的方式是從工廠類中獲取靜態(tài)方法--> ?
? ? <bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean> ?
? ? ?
實(shí)例工廠的方法注入
實(shí)例工廠的意思是獲取對(duì)象實(shí)例的方法不是靜態(tài)的,所以你需要首先new工廠類,再調(diào)用普通的實(shí)例方法:
Java代碼 ?收藏代碼
public class DaoFactory { ?
? ? //實(shí)例工廠 ?
? ? public FactoryDao getFactoryDaoImpl(){ ?
? ? ? ? return new FactoryDaoImpl(); ?
? ? } ?
} ?
那么下面這個(gè)類沒什么說的,跟前面也很相似,但是我們需要通過實(shí)例工廠類創(chuàng)建FactoryDao對(duì)象:
Java代碼 ?收藏代碼
public class SpringAction { ?
? ? //注入對(duì)象 ?
? ? private FactoryDao factoryDao; ?
? ? ??
? ? public void factoryOk(){ ?
? ? ? ? factoryDao.saveFactory(); ?
? ? } ?
??
? ? public void setFactoryDao(FactoryDao factoryDao) { ?
? ? ? ? this.factoryDao = factoryDao; ?
? ? } ?
} ?
?
最后看spring配置文件:
Xml代碼 ?收藏代碼
<!--配置bean,配置后該類由spring管理--> ?
? ? <bean name="springAction" class="com.bless.springdemo.action.SpringAction"> ?
? ? ? ? <!--(4)使用實(shí)例工廠的方法注入對(duì)象,對(duì)應(yīng)下面的配置文件(4)--> ?
? ? ? ? <property name="factoryDao" ref="factoryDao"></property> ?
? ? </bean> ?
? ? ??
? ? <!--(4)此處獲取對(duì)象的方式是從工廠類中獲取實(shí)例方法--> ?
? ? <bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"></bean> ?
? ? <bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean> ?
?
總結(jié)
Spring IOC注入方式用得最多的是(1)(2)種,多謝多練就會(huì)非常熟練。
? ? ? ? 另外注意:通過Spring創(chuàng)建的對(duì)象默認(rèn)是單例的,如果需要?jiǎng)?chuàng)建多實(shí)例對(duì)象可以在<bean>標(biāo)簽后面添加一個(gè)屬性:
Java代碼 ?收藏代碼
<bean name="..." class="..." scope="prototype">?
========
深度理解依賴注入
http://kb.cnblogs.com/page/45266/4/摘要:提到依賴注入,大家都會(huì)想到老馬那篇經(jīng)典的文章。其實(shí),本文就是相當(dāng)于對(duì)那篇文章的解讀。所以,如果您對(duì)原文已經(jīng)有了非常深刻的理解,完全不需要再看此文;但是,如果您和筆者一樣,以前曾經(jīng)看過,似乎看懂了,但似乎又沒抓到什么要領(lǐng),不妨看看筆者這個(gè)解讀,也許對(duì)您理解原文有一定幫助。
[1] 依賴在哪里
[2] DI的實(shí)現(xiàn)方式
[3] Setter Injection
[4] 除了DI,還有Service Locator
1.依賴在哪里
? ?老馬舉了一個(gè)小例子,是開發(fā)一個(gè)電影列舉器(MovieList),這個(gè)電影列舉器需要使用一個(gè)電影查找器(MovieFinder)提供的服務(wù),偽碼如下:
?1/*服務(wù)的接口*/
?2public interface MovieFinder {
?3 ? ?ArrayList findAll();
?4}
?5
?6/*服務(wù)的消費(fèi)者*/
?7class MovieLister
?8{
?9 ? ?public Movie[] moviesDirectedBy(String arg) {
10 ? ? ? ?List allMovies = finder.findAll();
11 ? ? ? ?for (Iterator it = allMovies.iterator(); it.hasNext();) {
12 ? ? ? ? ? ?Movie movie = (Movie) it.next();
13 ? ? ? ? ? ?if (!movie.getDirector().equals(arg)) it.remove();
14 ? ? ? ?}
15 ? ? ? ?return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
16 ? ?}
17
18 ? ?/*消費(fèi)者內(nèi)部包含一個(gè)將指向具體服務(wù)類型的實(shí)體對(duì)象*/
19 ? ?private MovieFinder finder;
20 ? ?/*消費(fèi)者需要在某一個(gè)時(shí)刻去實(shí)例化具體的服務(wù)。這是我們要解耦的關(guān)鍵所在,
21 ? ? *因?yàn)檫@樣的處理方式造成了服務(wù)消費(fèi)者和服務(wù)提供者的強(qiáng)耦合關(guān)系(這種耦合是在編譯期就確定下來的)。
22 ? ? **/
23 ? ?public MovieLister() {
24 ? ? ? ?finder = new ColonDelimitedMovieFinder("movies1.txt");
25 ? ?}
26}
從上面代碼的注釋中可以看到,MovieLister和ColonDelimitedMovieFinder(這可以使任意一個(gè)實(shí)現(xiàn)了MovieFinder接口的類型)之間存在強(qiáng)耦合關(guān)系,如下圖所示:
圖1
這使得MovieList很難作為一個(gè)成熟的組件去發(fā)布,因?yàn)樵诓煌膽?yīng)用環(huán)境中(包括同一套軟件系統(tǒng)被不同用戶使用的時(shí)候),它所要依賴的電影查找器可能是千差萬別的。所以,為了能實(shí)現(xiàn)真正的基于組件的開發(fā),必須有一種機(jī)制能同時(shí)滿足下面兩個(gè)要求:
?(1)解除MovieList對(duì)具體MoveFinder類型的強(qiáng)依賴(編譯期依賴)。
?(2)在運(yùn)行的時(shí)候?yàn)镸ovieList提供正確的MovieFinder類型的實(shí)例。
? ?換句話說,就是在運(yùn)行的時(shí)候才產(chǎn)生MovieList和MovieFinder之間的依賴關(guān)系(把這種依賴關(guān)系在一個(gè)合適的時(shí)候“注入”運(yùn)行時(shí)),這恐怕就是Dependency Injection這個(gè)術(shù)語的由來。再換句話說,我們提到過解除強(qiáng)依賴,這并不是說MovieList和MovieFinder之間的依賴關(guān)系不存在了,事實(shí)上MovieList無論如何也需要某類MovieFinder提供的服務(wù),我們只是把這種依賴的建立時(shí)間推后了,從編譯器推遲到運(yùn)行時(shí)了。
? ?依賴關(guān)系在OO程序中是廣泛存在的,只要A類型中用到了B類型實(shí)例,A就依賴于B。前面筆者談到的內(nèi)容是把概念抽象到了服務(wù)使用者和服務(wù)提供者的角度,這也符合現(xiàn)在SOA的設(shè)計(jì)思路。從另一種抽象方式上來看,可以把MovieList看成我們要構(gòu)建的主系統(tǒng),而MovieFinder是系統(tǒng)中的plugin,主系統(tǒng)并不強(qiáng)依賴于任何一個(gè)插件,但一旦插件被加載,主系統(tǒng)就應(yīng)該可以準(zhǔn)確調(diào)用適當(dāng)插件的功能。
? ?其實(shí)不管是面向服務(wù)的編程模式,還是基于插件的框架式編程,為了實(shí)現(xiàn)松耦合(服務(wù)調(diào)用者和提供者之間的or框架和插件之間的),都需要在必要的位置實(shí)現(xiàn)面向接口編程,在此基礎(chǔ)之上,還應(yīng)該有一種方便的機(jī)制實(shí)現(xiàn)具體類型之間的運(yùn)行時(shí)綁定,這就是DI所要解決的問題。
2.DI的實(shí)現(xiàn)方式
? ?和上面的圖1對(duì)應(yīng)的是,如果我們的系統(tǒng)實(shí)現(xiàn)了依賴注入,組件間的依賴關(guān)系就變成了圖2:
圖2
說白了,就是要提供一個(gè)容器,由容器來完成(1)具體ServiceProvider的創(chuàng)建(2)ServiceUser和ServiceProvider的運(yùn)行時(shí)綁定。下面我們就依次來看一下三種典型的依賴注入方式的實(shí)現(xiàn)。特別要說明的是,要理解依賴注入的機(jī)制,關(guān)鍵是理解容器的實(shí)現(xiàn)方式。本文后面給出的容器參考實(shí)現(xiàn),均為黃忠成老師的代碼,筆者僅在其中加上了一些關(guān)鍵注釋而已。
2.1 Constructor Injection(構(gòu)造器注入)
?我們可以看到,在整個(gè)依賴注入的數(shù)據(jù)結(jié)構(gòu)中,涉及到的重要的類型就是ServiceUser, ServiceProvider和Assembler三者,而這里所說的構(gòu)造器,指的是ServiceUser的構(gòu)造器。也就是說,在構(gòu)造ServiceUser實(shí)例的時(shí)候,才把真正的ServiceProvider傳給他:
?
1class MovieLister
2{
3 ? //其他內(nèi)容,省略
4
5 ? public MovieLister(MovieFinder finder)
6 ? {
7 ? ? ? this.finder = finder;
8 ? }
9}
接下來我們看看Assembler應(yīng)該如何構(gòu)建:
?1private MutablePicoContainer configureContainer() {
?2 ? ?MutablePicoContainer pico = new DefaultPicoContainer();
?3 ? ?
?4 ? ?//下面就是把ServiceProvider和ServiceUser都放入容器的過程,以后就由容器來提供ServiceUser的已完成依賴注入實(shí)例,
?5 ? ?//其中用到的實(shí)例參數(shù)和類型參數(shù)一般是從配置檔中讀取的,這里是個(gè)簡(jiǎn)單的寫法。
?6 ? ?//所有的依賴注入方法都會(huì)有類似的容器初始化過程,本文在后面的小節(jié)中就不再重復(fù)這一段代碼了。
?7 ? ?Parameter[] finderParams = ?{new ConstantParameter("movies1.txt")};
?8 ? ?pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
?9 ? ?pico.registerComponentImplementation(MovieLister.class);
10 ? ?//至此,容器里面裝入了兩個(gè)類型,其中沒給出構(gòu)造參數(shù)的那一個(gè)(MovieLister)將依靠其在構(gòu)造器中定義的傳入?yún)?shù)類型,在容器中
11 ? ?//進(jìn)行查找,找到一個(gè)類型匹配項(xiàng)即可進(jìn)行構(gòu)造初始化。
12 ? ?return pico;
13}
需要在強(qiáng)調(diào)一下的是,依賴并未消失,只是延后到了容器被構(gòu)建的時(shí)刻。所以正如圖2中您已經(jīng)看到的,容器本身(更準(zhǔn)確的說,是一個(gè)容器運(yùn)行實(shí)例的構(gòu)建過程)對(duì)ServiceUser和ServiceProvoder都是存在依賴關(guān)系的。所以,在這樣的體系結(jié)構(gòu)里,ServiceUser、ServiceProvider和容器都是穩(wěn)定的,互相之間也沒有任何依賴關(guān)系;所有的依賴關(guān)系、所有的變化都被封裝進(jìn)了容器實(shí)例的創(chuàng)建過程里,符合我們對(duì)服務(wù)應(yīng)用的理解。而且,在實(shí)際開發(fā)中我們一般會(huì)采用配置文件來輔助容器實(shí)例的創(chuàng)建,將這種變化性排斥到編譯期之外。
? ?即使還沒給出后面的代碼,你也一定猜得到,這個(gè)container類一定有一個(gè)GetInstance(Type t)這樣的方法,這個(gè)方法會(huì)為我們返回一個(gè)已經(jīng)注入完畢的MovieLister。 一個(gè)簡(jiǎn)單的應(yīng)用如下:
1public void testWithPico()?
2{
3 ? ?MutablePicoContainer pico = configureContainer();
4 ? ?MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
5 ? ?Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
6 ? ?assertEquals("Once Upon a Time in the West", movies[0].getTitle());
7}
上面最關(guān)鍵的就是對(duì)pico.getComponentInstance的調(diào)用。Assembler會(huì)在這個(gè)時(shí)候調(diào)用MovieLister的構(gòu)造器,構(gòu)造器的參數(shù)就是當(dāng)時(shí)通過pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)設(shè)置進(jìn)去的實(shí)際的ServiceProvider--ColonMovieFinder。下面請(qǐng)看這個(gè)容器的參考代碼:
2.2 Setter Injection(設(shè)值注入)
? ?這種注入方式和構(gòu)造注入實(shí)在很類似,唯一的區(qū)別就是前者在構(gòu)造函數(shù)的調(diào)用過程中進(jìn)行注入,而它是通過給屬性賦值來進(jìn)行注入。無怪乎PicoContainer和Spring都是同時(shí)支持這兩種注入方式。Spring對(duì)通過XML進(jìn)行配置有比較好的支持,也使得Spring中更常使用設(shè)值注入的方式:
?1<beans>
?2 ? ?<bean id="MovieLister" class="spring.MovieLister">
?3 ? ? ? ?<property name="finder">
?4 ? ? ? ? ? ?<ref local="MovieFinder"/>
?5 ? ? ? ?property>
?6 ? ?bean>
?7 ? ?<bean id="MovieFinder" class="spring.ColonMovieFinder">
?8 ? ? ? ?<property name="filename">
?9 ? ? ? ? ? ?<value>movies1.txtvalue>
10 ? ? ? ?property>
11 ? ?bean>
12beans>
下面也給出支持設(shè)值注入的容器參考實(shí)現(xiàn),大家可以和構(gòu)造器注入的容器對(duì)照起來看,里面的差別很小,主要的差別就在于,在獲取對(duì)象實(shí)例(GetInstance)的時(shí)候,前者是通過反射得到待創(chuàng)建類型的構(gòu)造器信息,然后根據(jù)構(gòu)造器傳入?yún)?shù)的類型在容器中進(jìn)行查找,并構(gòu)造出合適的實(shí)例;而后者是通過反射得到待創(chuàng)建類型的所有屬性,然后根據(jù)屬性的類型在容器中查找相應(yīng)類型的實(shí)例。
設(shè)值注入的容器實(shí)現(xiàn)偽碼
2.3 Interface Injection (接口注入)
? ?這是筆者認(rèn)為最不夠優(yōu)雅的一種依賴注入方式。要實(shí)現(xiàn)接口注入,首先ServiceProvider要給出一個(gè)接口定義:
1public interface InjectFinder {
2 ? ?void injectFinder(MovieFinder finder);
3}
接下來,ServiceUser必須實(shí)現(xiàn)這個(gè)接口:
1class MovieLister: InjectFinder
2{
3 ? public void injectFinder(MovieFinder finder) {
4 ? ? ?this.finder = finder;
5 ? ?}
6}
容器所要做的,就是根據(jù)接口定義調(diào)用其中的inject方法完成注入過程,這里就不在贅述了,總的原理和上面兩種依賴注入模式?jīng)]有太多區(qū)別。
2.4 ?除了DI,還有Service Locator
? ?上面提到的依賴注入只是消除ServiceUser和ServiceProvider之間的依賴關(guān)系的一種方法,還有另一種方法:服務(wù)定位器(Service Locator)。也就是說,由ServiceLocator來專門負(fù)責(zé)提供具體的ServiceProvider。當(dāng)然,這樣的話ServiceUser不僅要依賴于服務(wù)的接口,還依賴于ServiceContract。仍然是最早提到過的電影列舉器的例子,如果使用Service Locator來解除依賴的話,整個(gè)依賴關(guān)系應(yīng)當(dāng)如下圖所示:
圖3
用起來也很簡(jiǎn)單,在一個(gè)適當(dāng)?shù)奈恢?#xff08;比如在一組相關(guān)服務(wù)即將被調(diào)用之前)對(duì)ServiceLocator進(jìn)行初始化,用到的時(shí)候就直接用ServiceLocator返回ServiceProvider實(shí)例:
?
1//服務(wù)定位器的初始化
2ServiceLocator locator = new ServiceLocator();
3locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));
4ServiceLocator.load(locator);
5//服務(wù)定義器的使用
6//其實(shí)這個(gè)使用方式體現(xiàn)了服務(wù)定位器和依賴注入模式的最大差別:ServiceUser需要顯示的調(diào)用ServiceLocator,從而獲取自己需要的服務(wù)對(duì)象;
7//而依賴注入則是隱式的由容器完成了這一切。
8MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");
9
正因?yàn)樯厦嫣岬竭^的ServiceUser對(duì)ServiceLocator的依賴性,從提高模塊的獨(dú)立性(比如說,你可能把你構(gòu)造的ServiceUser或者ServiceProvider給第三方使用)上來說,依賴注入可能更好一些,這恐怕也是為什么大多數(shù)的IOC框架都選用了DI的原因。ServiceLocator最大的優(yōu)點(diǎn)可能在于實(shí)現(xiàn)起來非常簡(jiǎn)單,如果您開發(fā)的應(yīng)用沒有復(fù)雜到需要采用一個(gè)IOC框架的程度,也許您可以試著采用它。
3.廣義的服務(wù)
? ?文中很多地方提到服務(wù)使用者(ServiceUser)和服務(wù)提供者(ServiceProvider)的概念,這里的“服務(wù)”是一種非常廣義的概念,在語法層面就是指最普通的依賴關(guān)系(類型A中有一個(gè)B類型的變量,則A依賴于B)。如果您把服務(wù)理解為WCF或者Web Service中的那種服務(wù)概念,您會(huì)發(fā)現(xiàn)上面所說的所有技術(shù)手段都是沒有意義的。以WCF而論,其客戶端和服務(wù)器端本就是依賴于Contract的松耦合關(guān)系,其實(shí)這也從另一個(gè)角度說明了SOA應(yīng)用的優(yōu)勢(shì)所在。
========
總結(jié)
- 上一篇: VC++ .Net 实例学习
- 下一篇: windbg查看设备栈设备树学习总结