依赖注入学习总结
控制反轉(zhuǎn)
同義詞 依賴注入一般指控制反轉(zhuǎn)控制反轉(zhuǎn)(Inversion of Control,英文縮寫為IoC)是框架的重要特征,并非面向?qū)ο缶幊痰膶S眯g(shù)語。它與依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)并沒有關(guān)系。
中文名 控制反轉(zhuǎn) 外文名 Inverse of Control 起源時間 1988年 目 ? ?的 描述框架的重要特征
目錄
1 起源
2 設(shè)計模式
3 優(yōu)缺點
4 實現(xiàn)初探
5 類型
6 實現(xiàn)策略
7 實現(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è)計模式》至少兩次使用了控制反轉(zhuǎn),[1.6.7設(shè)計應(yīng)支持變化]和[5.10模板方法模式]。[1]?
2004年,Martin Fowler在其著名文章Inversion of Control Containers and the Dependency Injection pattern中[2] ?,使用了該術(shù)語。
但是,這些使用案例也使得IoC的含義變得含混。
設(shè)計模式
IoC可以認為是一種全新的設(shè)計模式,但是理論和時間成熟相對較晚,并沒有包含在GoF中。
Interface Driven Design接口驅(qū)動,接口驅(qū)動有很多好處,可以提供不同靈活的子類實現(xiàn),增加代碼穩(wěn)定和健壯性等等,但是接口一定是需要實現(xiàn)的,也就是如下語句遲早要執(zhí)行:AInterface a = new AInterfaceImp(); 這樣一來,耦合關(guān)系就產(chǎn)生了,如:
classA {AInterface a;A(){}AMethod()//一個方法{a = new AInterfaceImp();} }
Class A與AInterfaceImp就是依賴關(guān)系,如果想使用AInterface的另外一個實現(xiàn)就需要更改代碼了。當(dāng)然我們可以建立一個Factory來根據(jù)條件生成想要的AInterface的具體實現(xiàn),即:
InterfaceImplFactory {AInterface create(Object condition){if(condition == condA){return new AInterfaceImpA();}else if(condition == condB){return new AInterfaceImpB();}else{return new AInterfaceImp();}} }
表面上是在一定程度上緩解了以上問題,但實質(zhì)上這種代碼耦合并沒有改變。通過IoC模式可以徹底解決這種耦合,它把耦合從代碼中移出去,放到統(tǒng)一的XML 文件中,通過一個容器在需要的時候把這個依賴關(guān)系形成,即把需要的接口實現(xiàn)注入到需要它的類中,這可能就是“依賴注入”說法的來源了。
IoC模式,系統(tǒng)中通過引入實現(xiàn)了IoC模式的IoC容器,即可由IoC容器來管理對象的生命周期、依賴關(guān)系等,從而使得應(yīng)用程序的配置和依賴性規(guī)范與實際的應(yīng)用程序代碼分開。其中一個特點就是通過文本的配置文件進行應(yīng)用程序組件間相互關(guān)系的配置,而不用重新修改并編譯具體的代碼。
當(dāng)前比較知名的IoC容器有:Pico Container、Avalon 、Spring、JBoss、HiveMind、EJB等。
在上面的幾個IoC容器中,輕量級的有Pico Container、Avalon、Spring、HiveMind等,超重量級的有EJB,而半輕半重的有容器有JBoss,Jdon等。
可以把IoC模式看做是工廠模式的升華,可以把IoC看作是一個大工廠,只不過這個大工廠里要生成的對象都是在XML文件中給出定義的,然后利用Java 的“反射”編程,根據(jù)XML中給出的類名生成相應(yīng)的對象。從實現(xiàn)來看,IoC是把以前在工廠方法里寫死的對象生成代碼,改變?yōu)橛蒟ML文件來定義,也就是把工廠和對象生成這兩者獨立分隔開來,目的就是提高靈活性和可維護性。
IoC中最基本的Java技術(shù)就是“反射”編程。反射又是一個生澀的名詞,通俗的說反射就是根據(jù)給出的類名(字符串)來生成對象。這種編程方式可以讓對象在生成時才決定要生成哪一種對象。反射的應(yīng)用是很廣泛的,像Hibernate、Spring中都是用“反射”做為最基本的技術(shù)手段。
在過去,反射編程方式相對于正常的對象生成方式要慢10幾倍,這也許也是當(dāng)時為什么反射技術(shù)沒有普遍應(yīng)用開來的原因。但經(jīng)SUN改良優(yōu)化后,反射方式生成對象和通常對象生成方式,速度已經(jīng)相差不大了(但依然有一倍以上的差距)。
優(yōu)缺點
IoC最大的好處是什么?因為把對象生成放在了XML里定義,所以當(dāng)我們需要換一個實現(xiàn)子類將會變成很簡單(一般這樣的對象都是實現(xiàn)于某種接口的),只要修改XML就可以了,這樣我們甚至可以實現(xiàn)對象的熱插拔(有點像USB接口和SCSI硬盤了)。
IoC最大的缺點是什么?(1)生成一個對象的步驟變復(fù)雜了(事實上操作上還是挺簡單的),對于不習(xí)慣這種方式的人,會覺得有些別扭和不直觀。(2)對象生成因為是使用反射編程,在效率上有些損耗。但相對于IoC提高的維護性和靈活性來說,這點損耗是微不足道的,除非某對象的生成對效率要求特別高。(3)缺少IDE重構(gòu)操作的支持,如果在Eclipse要對類改名,那么你還需要去XML文件里手工去改了,這似乎是所有XML方式的缺陷所在。
實現(xiàn)初探
IOC關(guān)注服務(wù)(或應(yīng)用程序部件)是如何定義的以及他們應(yīng)該如何定位他們依賴的其它服務(wù)。通常,通過一個容器或定位框架來獲得定義和定位的分離,容器或定位框架負責(zé):
保存可用服務(wù)的集合
提供一種方式將各種部件與它們依賴的服務(wù)綁定在一起
為應(yīng)用程序代碼提供一種方式來請求已配置的對象(例如,一個所有依賴都滿足的對象), 這種方式可以確保該對象需要的所有相關(guān)的服務(wù)都可用。
類型
現(xiàn)有的框架實際上使用以下三種基本技術(shù)的框架執(zhí)行服務(wù)和部件間的綁定:
類型1 (基于接口): 可服務(wù)的對象需要實現(xiàn)一個專門的接口,該接口提供了一個對象,可以重用這個對象查找依賴(其它服務(wù))。早期的容器Excalibur使用這種模式。
類型2 (基于setter): 通過JavaBean的屬性(setter方法)為可服務(wù)對象指定服務(wù)。HiveMind和Spring采用這種方式。
類型3 (基于構(gòu)造函數(shù)): 通過構(gòu)造函數(shù)的參數(shù)為可服務(wù)對象指定服務(wù)。PicoContainer只使用這種方式。HiveMind和Spring也使用這種方式。
實現(xiàn)策略
IoC是一個很大的概念,可以用不同的方式實現(xiàn)。其主要形式有兩種:
◇依賴查找:容器提供回調(diào)接口和上下文條件給組件。EJB和Apache Avalon 都使用這種方式。這樣一來,組件就必須使用容器提供的API來查找資源和協(xié)作對象,僅有的控制反轉(zhuǎn)只體現(xiàn)在那些回調(diào)方法上(也就是上面所說的 類型1):容器將調(diào)用這些回調(diào)方法,從而讓應(yīng)用代碼獲得相關(guān)資源。
◇依賴注入:組件不做定位查詢,只提供普通的Java方法讓容器去決定依賴關(guān)系。容器全權(quán)負責(zé)的組件的裝配,它會把符合依賴關(guān)系的對象通過JavaBean屬性或者構(gòu)造函數(shù)傳遞給需要的對象。通過JavaBean屬性注射依賴關(guān)系的做法稱為設(shè)值方法注入(Setter Injection);將依賴關(guān)系作為構(gòu)造函數(shù)參數(shù)傳入的做法稱為構(gòu)造器注入(Constructor Injection)
實現(xiàn)方式
實現(xiàn)數(shù)據(jù)訪問層
數(shù)據(jù)訪問層有兩個目標(biāo)。第一是將數(shù)據(jù)庫引擎從應(yīng)用中抽象出來,這樣就可以隨時改變數(shù)據(jù)庫—比方說,從微軟SQL變成Oracle。不過在實踐上很少會這么做,也沒有足夠的理由未來使用實現(xiàn)數(shù)據(jù)訪問層而進行重構(gòu)現(xiàn)有應(yīng)用的努力。[3]?
第二個目標(biāo)是將數(shù)據(jù)模型從數(shù)據(jù)庫實現(xiàn)中抽象出來。這使得數(shù)據(jù)庫或代碼開源根據(jù)需要改變,同時只會影響主應(yīng)用的一小部分——數(shù)據(jù)訪問層。這一目標(biāo)是值得的,為了在現(xiàn)有系統(tǒng)中實現(xiàn)它進行必要的重構(gòu)。
模塊與接口重構(gòu)
依賴注入背后的一個核心思想是單一功能原則(single responsibility principle)。該原則指出,每一個對象應(yīng)該有一個特定的目的,而應(yīng)用需要利用這一目的的不同部分應(yīng)當(dāng)使用合適的對象。這意味著這些對象在系統(tǒng)的任何地方都可以重用。但在現(xiàn)有系統(tǒng)里面很多時候都不是這樣的。[3]?
隨時增加單元測試
把功能封裝到整個對象里面會導(dǎo)致自動測試困難或者不可能。將模塊和接口與特定對象隔離,以這種方式重構(gòu)可以執(zhí)行更先進的單元測試。按照后面再增加測試的想法繼續(xù)重構(gòu)模塊是誘惑力的,但這是錯誤的。[3]?
使用服務(wù)定位器而不是構(gòu)造注入
實現(xiàn)控制反轉(zhuǎn)不止一種方法。最常見的辦法是使用構(gòu)造注入,這需要在對象首次被創(chuàng)建是提供所有的軟件依賴。然而,構(gòu)造注入要假設(shè)整個系統(tǒng)都使用這一模式,這意味著整個系統(tǒng)必須同時進行重構(gòu)。這很困難、有風(fēng)險,且耗時。
========
依賴注入原理(為什么需要依賴注入)
http://blog.csdn.net/coderder/article/details/51897721目錄(?)[-]
0 前言
1 為什么需要依賴注入
2 依賴注入的實現(xiàn)方式
21 構(gòu)造函數(shù)注入Contructor Injection
22 setter注入
23 接口注入
3 最后
參考
0. 前言
在軟件工程領(lǐng)域,依賴注入(Dependency Injection)是用于實現(xiàn)控制反轉(zhuǎn)(Inversion of Control)的最常見的方式之一。本文主要介紹依賴注入原理和常見的實現(xiàn)方式,重點在于介紹這種年輕的設(shè)計模式的適用場景及優(yōu)勢。
1. 為什么需要依賴注入
控制反轉(zhuǎn)用于解耦,解的究竟是誰和誰的耦?這是我在最初了解依賴注入時候產(chǎn)生的第一個問題。
下面我引用Martin Flower在解釋介紹注入時使用的一部分代碼來說明這個問題。
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)建了一個名為MovieLister的類來提供需要的電影列表,它moviesDirectedBy方法提供根據(jù)導(dǎo)演名來搜索電影的方式。真正負責(zé)搜索電影的是實現(xiàn)了MovieFinder接口的MovieFinderImpl,我們的MovieLister類在構(gòu)造函數(shù)中創(chuàng)建了一個MovieFinderImpl的對象。
目前看來,一切都不錯。但是,當(dāng)我們希望修改finder,將finder替換為一種新的實現(xiàn)時(比如為MovieFinder增加一個參數(shù)表明Movie數(shù)據(jù)的來源是哪個數(shù)據(jù)庫),我們不僅需要修改MovieFinderImpl類,還需要修改我們MovieLister中創(chuàng)建MovieFinderImpl的代碼。
這就是依賴注入要處理的耦合。這種在MovieLister中創(chuàng)建MovieFinderImpl的方式,使得MovieLister不僅僅依賴于MovieFinder這個接口,它還依賴于MovieListImpl這個實現(xiàn)。 這種在一個類中直接創(chuàng)建另一個類的對象的代碼,和硬編碼(hard-coded strings)以及硬編碼的數(shù)字(magic numbers)一樣,是一種導(dǎo)致耦合的壞味道,我們可以把這種壞味道稱為硬初始化(hard init)。同時,我們也應(yīng)該像記住硬編碼一樣記住,new(對象創(chuàng)建)是有毒的。
Hard Init帶來的主要壞處有兩個方面:1)上文所述的修改其實現(xiàn)時,需要修改創(chuàng)建處的代碼;2)不便于測試,這種方式創(chuàng)建的類(上文中的MovieLister)無法單獨被測試,其行為和MovieFinderImpl緊緊耦合在一起,同時,也會導(dǎo)致代碼的可讀性問題(“如果一段代碼不便于測試,那么它一定不便于閱讀。”)。
2. 依賴注入的實現(xiàn)方式
依賴注入其實并不神奇,我們?nèi)粘5拇a中很多都用到了依賴注入,但很少注意到它,也很少主動使用依賴注入進行解耦。這里我們簡單介紹一下賴注入實現(xiàn)三種的方式。
2.1 構(gòu)造函數(shù)注入(Contructor Injection)
這是我認為的最簡單的依賴注入方式,我們修改一下上面代碼中MovieList的構(gòu)造函數(shù),使得MovieFinderImpl的實現(xiàn)在MovieLister類之外創(chuàng)建。這樣,MovieLister就只依賴于我們定義的MovieFinder接口,而不依賴于MovieFinder的實現(xiàn)了。
public class MovieLister {private MovieFinder finder;public MovieLister(MovieFinder finder) {this.finder = finder;}... }
2.2 setter注入
類似的,我們可以增加一個setter函數(shù)來傳入創(chuàng)建好的MovieFinder對象,這樣同樣可以避免在MovieFinder中hard init這個對象。
public class MovieLister {s...public void setFinder(MovieFinder finder) {this.finder = finder;} }
2.3 接口注入
接口注入使用接口來提供setter方法,其實現(xiàn)方式如下。
首先要創(chuàng)建一個注入使用的接口。
public interface InjectFinder {
? ? void injectFinder(MovieFinder finder);}
之后,我們讓MovieLister實現(xiàn)這個接口。
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)點,以及.NET平臺上依賴注入的各種框架和用法。在這本書的開頭,講述了軟件工程中的一個重要的理念就是關(guān)注分離(Separation of concern, SoC)。依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開發(fā)出松散耦合(loose coupled)、可維護、可測試的代碼和程序。這條原則的做法是大家熟知的面向接口,或者說是面向抽象編程。
關(guān)于什么是依賴注入,在Stack Overflow上面有一個問題,如何向一個5歲的小孩解釋依賴注入,其中得分最高的一個答案是:
“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ù)。
編寫松耦合的代碼說起來很簡單,但是實際上寫著寫著就變成了緊耦合。
使用例子來說明可能更簡潔明了,首先來看看什么樣的代碼是緊耦合。
1 不好的實現(xiàn)
編寫松耦合代碼的第一步,可能大家都熟悉,那就是對系統(tǒng)分層。比如下面的經(jīng)典的三層架構(gòu)。
Classic 3-tier architecture
分完層和實現(xiàn)好是兩件事情,并不是說分好層之后就能夠松耦合了。
1.1 緊耦合的代碼
有很多種方式來設(shè)計一個靈活的,可維護的復(fù)雜應(yīng)用,但是n層架構(gòu)是一種大家比較熟悉的方式,這里面的挑戰(zhàn)在于如何正確的實現(xiàn)n層架構(gòu)。
假設(shè)要實現(xiàn)一個很簡單的電子商務(wù)網(wǎng)站,要列出商品列表,如下:
product list page
下面就具體來演示通常的做法,是如何一步一步把代碼寫出緊耦合的。
1.1.1 數(shù)據(jù)訪問層
要實現(xiàn)商品列表這一功能,首先要編寫數(shù)據(jù)訪問層,需要設(shè)計數(shù)據(jù)庫及表,在SQLServer中設(shè)計的數(shù)據(jù)庫表Product結(jié)構(gòu)如下:
Product Table?
表設(shè)計好之后,就可以開始寫代碼了。在Visual Studio 中,新建一個名為DataAccessLayer的工程,添加一個ADO.NET Entity Data Model,此時Visual Studio的向?qū)詣訋臀覀兩蒔roduct實體和ObjectContext DB操作上下文。這樣我們的 Data Access Layer就寫好了。
Product Entity Model
1.1.2 業(yè)務(wù)邏輯層
表現(xiàn)層實際上可以直接訪問數(shù)據(jù)訪問層,通過ObjectContext 獲取Product 列表。但是大多數(shù)情況下,我們不是直接把DB里面的數(shù)據(jù)展現(xiàn)出來,而是需要對數(shù)據(jù)進行處理,比如對會員,需要對某些商品的價格打折。這樣我們就需要業(yè)務(wù)邏輯層,來處理這些與具體業(yè)務(wù)邏輯相關(guān)的事情。
新建一個類庫,命名為DomainLogic,然后添加一個名為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)實現(xiàn)了。
1.1.3 表現(xiàn)層
現(xiàn)在實現(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)”我們的代碼寫好了,并且也達到了要求。整個項目的結(jié)構(gòu)如下圖:
?Solution layout
這應(yīng)該是我們通常經(jīng)常寫的所謂的三層架構(gòu)。在Visual Studio中,三層之間的依賴可以通過項目引用表現(xiàn)出來。
1.2.1 依賴關(guān)系圖
現(xiàn)在我們來分析一下,這三層之間的依賴關(guān)系,很明顯,上面的實現(xiàn)中,DomianLogic需要依賴SqlDataAccess,因為DomainLogic中用到了Product這一實體,而這個實體是定義在DataAccess這一層的。WebUI這一層需要依賴DomainLogic,因為ProductService在這一層,同時,還需要依賴DataAccess,因為在UI中也用到了Product實體,現(xiàn)在整個系統(tǒng)的依賴關(guān)系是這樣的:
Dependency graph in three-tier architecture
1.2.2 耦合性分析
使用三層結(jié)構(gòu)的主要目的是分離關(guān)注點,當(dāng)然還有一個原因是可測試性。我們應(yīng)該將領(lǐng)域模型從數(shù)據(jù)訪問層和表現(xiàn)層中分離出來,這樣這兩個層的變化才不會污染領(lǐng)域模型。在大的系統(tǒng)中,這點很重要,這樣才能將系統(tǒng)中的不同部分隔離開來。
現(xiàn)在來看之前的實現(xiàn)中,有沒有模塊性,有沒有那個模塊可以隔離出來呢。現(xiàn)在添加幾個新的case來看,系統(tǒng)是否能夠響應(yīng)這些需求:
添加新的用戶界面
除了WebForm用戶之外,可能還需要一個WinForm的界面,現(xiàn)在我們能否復(fù)用領(lǐng)域?qū)雍蛿?shù)據(jù)訪問層呢?從依賴圖中可以看到,沒有任何一個模塊會依賴表現(xiàn)層,因此很容易實現(xiàn)這一點變化。我們只需要創(chuàng)建一個WPF的富客戶端就可以。現(xiàn)在整個系統(tǒng)的依賴圖如下:
WPF client
更換新的數(shù)據(jù)源
可能過了一段時間,需要把整個系統(tǒng)部署到云上,要使用其他的數(shù)據(jù)存儲技術(shù),比如Azure Table Storage Service。現(xiàn)在,整個訪問數(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)層都需要進行相應(yīng)的修改。
1.2.3 問題
除了上面的各層之間耦合下過強之外,代碼中還有其他問題。
領(lǐng)域模型似乎都寫到了數(shù)據(jù)訪問層中。所以領(lǐng)域模型看起來依賴了數(shù)據(jù)訪問層。在數(shù)據(jù)訪問層中定義了名為Product的類,這種類應(yīng)該是屬于領(lǐng)域模型層的。
表現(xiàn)層中摻入了決定某個用戶是否是會員的邏輯。這種業(yè)務(wù)邏輯應(yīng)該是 業(yè)務(wù)邏輯層中應(yīng)該處理的,所以也應(yīng)該放到領(lǐng)域模型層
ProductService因為依賴了數(shù)據(jù)訪問層,所以也會依賴在web.config 中配置的數(shù)據(jù)庫連接字符串等信息。這使得,整個業(yè)務(wù)邏輯層也需要依賴這些配置才能正常運行。
在View中,包含了太多了函數(shù)性功能。他執(zhí)行了強制類型轉(zhuǎn)換,字符串格式化等操作,這些功能應(yīng)該是在界面顯示得模型中完成。
上面可能是我們大多數(shù)寫代碼時候的實現(xiàn), UI界面層去依賴了數(shù)據(jù)訪問層,有時候偷懶就直接引用了這一層,因為實體定義在里面了。業(yè)務(wù)邏輯層也是依賴數(shù)據(jù)訪問層,直接在業(yè)務(wù)邏輯里面使用了數(shù)據(jù)訪問層里面的實體。這樣使得整個系統(tǒng)緊耦合,并且可測試性差。那現(xiàn)在我們看看,如何修改這樣一個系統(tǒng),使之達到松散耦合,從而提高可測試性呢?
2 較好的實現(xiàn)
依賴注入能夠較好的解決上面出現(xiàn)的問題,現(xiàn)在可以使用這一思想來重新實現(xiàn)前面的系統(tǒng)。之所以重新實現(xiàn)是因為,前面的實現(xiàn)在一開始的似乎就沒有考慮到擴展性和松耦合,使用重構(gòu)的方式很難達到理想的效果。對于小的系統(tǒng)來說可能還可以,但是對于一個大型的系統(tǒng),應(yīng)該是比較困難的。
在寫代碼的時候,要管理好依賴性,在前面的實現(xiàn)這種,代碼直接控制了依賴性:當(dāng)ProductService需要一個ObjectContext類的似乎,直接new了一個,當(dāng)HomeController需要一個ProductService的時候,直接new了一個,這樣看起來很酷很方便,實際上使得整個系統(tǒng)具有很大的局限性,變得緊耦合。new 操作實際上就引入了依賴, 控制反轉(zhuǎn)這種思想就是要使的我們比較好的管理依賴。
2.1 松耦合的代碼
2.1.1 表現(xiàn)層
首先從表現(xiàn)層來分析,表現(xiàn)層主要是用來對數(shù)據(jù)進行展現(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)層代碼相比,要整潔很多。很明顯是不需要進行類型轉(zhuǎn)換,要實現(xiàn)這樣的目的,只需要讓Index.aspx這個視圖繼承自 System.Web.Mvc.ViewPage<FeaturedProductsViewModel> 即可,當(dāng)我們在從Controller創(chuàng)建View的時候,可以進行選擇,然后會自動生成。整個用于展示的信息放在了SummaryText字段中。
這里就引入了一個視圖模型(View-Specific Models),他封裝了視圖的行為,這些模型只是簡單的POCOs對象(Plain Old CLR Objects)。FeatureProductsViewModel中包含了一個List列表,每個元素是一個ProductViewModel類,其中定義了一些簡單的用于數(shù)據(jù)展示的字段。
FeatureProductsViewModel
現(xiàn)在在Controller中,我們只需要給View返回FeatureProductsViewModel對象即可。比如:
public ViewResult Index()
{
? ? var vm = new FeaturedProductsViewModel();
? ? return View(vm);
}
現(xiàn)在返回的是空列表,具體的填充方式在領(lǐng)域模型中,我們接著看領(lǐng)域模型層。
2.1.2 領(lǐng)域邏輯層
新建一個類庫,這里面包含POCOs和一些抽象類型。POCOs用來對領(lǐng)域建模,抽象類型提供抽象作為到達領(lǐng)域模型的入口。依賴注入的原則是面向接口而不是具體的類編程,使得我們可以替換具體實現(xiàn)。
現(xiàn)在我們需要為表現(xiàn)層提供數(shù)據(jù)。因此用戶界面層需要引用領(lǐng)域模型層。對數(shù)據(jù)訪問層的簡單抽象可以采用Patterns of Enterprise Application Architecture一書中講到的Repository模式。因此定義一個ProductRepository抽象類,注意是抽象類,在領(lǐng)域模型庫中。它定義了一個獲取所有特價商品的抽象方法:
public abstract class ProductRepository
{
? ? public abstract IEnumerable<Product> GetFeaturedProducts();
}
這個方法的Product類中只定義了商品的基本信息比如名稱和單價。整個關(guān)系圖如下:
Domain model?
現(xiàn)在來看表現(xiàn)層,HomeController中的Index方法應(yīng)該要使用ProductService實例類來獲取商品列表,執(zhí)行價格打折,并且把Product類似轉(zhuǎn)化為ProductViewModel實例,并將該實例加入到FeaturesProductsViewModel中。因為ProductService有一個帶有類型為ProductReposity抽象類的構(gòu)造函數(shù),所以這里可以通過構(gòu)造函數(shù)注入實現(xiàn)了ProductReposity抽象類的實例。這里和之前的最大區(qū)別是,我們沒有使用new關(guān)鍵字來立即new一個對象,而是通過構(gòu)造函數(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ù)中,傳入了實現(xiàn)了ProductRepository抽象類的一個實例,然后將該實例保存在定義的私有的只讀的ProductRepository類型的repository對象中,這就是典型的通過構(gòu)造函數(shù)注入。在Index方法中,獲取數(shù)據(jù)的ProductService類中的主要功能,實際上是通過傳入的repository類來代理完成的。
ProductService類是一個純粹的領(lǐng)域?qū)ο?#xff0c;實現(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ù)注入的方式,保存了實現(xiàn)了ProductReposity抽象類的實例,然后借助該實例中的GetFeatureProducts方法,獲取原始列表數(shù)據(jù),然后進行打折處理,進而實現(xiàn)了自己的GetFeaturedProducts方法。在該GetFeaturedProducts方法中,跟之前不同的地方在于,現(xiàn)在的參數(shù)是IPrincipal,而不是之前的bool型,因為判斷用戶的狀況,這是一個業(yè)務(wù)邏輯,不應(yīng)該在表現(xiàn)層處理。IPrincipal是BCL中的類型,所以不存在額外的依賴。我們應(yīng)該基于接口編程IPrincipal是應(yīng)用程序用戶的一種標(biāo)準(zhǔn)方式。
這里將IPrincipal作為參數(shù)傳遞給某個方法,然后再里面調(diào)用實現(xiàn)的方式是依賴注入中的方法注入的手段。和構(gòu)造函數(shù)注入一樣,同樣是將內(nèi)部實現(xiàn)代理給了傳入的依賴對象。
現(xiàn)在我們只剩下兩塊地方?jīng)]有處理了:
沒有ProductRepository的具體實現(xiàn),這個很容易實現(xiàn),后面放到數(shù)據(jù)訪問層里面去處理,我們只需要創(chuàng)建一個具體的實現(xiàn)了ProductRepository的數(shù)據(jù)訪問類即可。
默認上,ASP.NET MVC 希望Controller對象有自己的默認構(gòu)造函數(shù),因為我們在HomeController中添加了新的構(gòu)造函數(shù)來注入依賴,所以MVC框架不知道如何解決創(chuàng)建實例,因為有依賴。這個問題可以通過開發(fā)一個IControllerFactory來解決,該對象可以創(chuàng)建一個具體的ProductRepositry實例,然后傳給HomeController這里不多講。
現(xiàn)在我們的領(lǐng)域邏輯層已經(jīng)寫好了。在該層,我們只操作領(lǐng)域模型對象,以及.NET BCL 中的基本對象。模型使用POCOs來表示,命名為Product。領(lǐng)域模型層必須能夠和外界進行交流(database),所以需要一個抽象類(Repository)來時完成這一功能,并且在必要的時候,可以替換具體實現(xiàn)。
2.1.3 數(shù)據(jù)訪問層
現(xiàn)在我們可以使用LINQ to Entity來實現(xiàn)具體的數(shù)據(jù)訪問層邏輯了。因為要實現(xiàn)領(lǐng)域模型的ProductRepository抽象類,所以需要引入領(lǐng)域模型層。注意,這里的依賴變成了數(shù)據(jù)訪問層依賴領(lǐng)域模型層。跟之前的恰好相反,代碼實現(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)域模型層中,我們定義了一個名為Product的領(lǐng)域模型,然后再數(shù)據(jù)訪問層中Entity Framework幫我們也生成了一個名為Product的數(shù)據(jù)訪問層實體,他是和db中的Product表一一對應(yīng)的。所以我們在方法返回的時候,需要把類型從db中的Product轉(zhuǎn)換為領(lǐng)域模型中的POCOs Product對象。
two product class in the system?
Domain Model中的Product是一個POCOs類型的對象,他僅僅包含領(lǐng)域模型中需要用到的一些基本字段,DataAccess中的Product對象是映射到DB中的實體,它包含數(shù)據(jù)庫中Product表定義的所有字段,在數(shù)據(jù)表現(xiàn)層中我們 定義了一個ProductViewModel數(shù)據(jù)展現(xiàn)的Model。
這兩個對象之間的轉(zhuǎ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)在,整個系統(tǒng)的依賴關(guān)系圖如下:
Dependency graph in DDD
表現(xiàn)層和數(shù)據(jù)訪問層都依賴領(lǐng)域模型層,這樣,在前面的case中,如果我們新添加一個UI界面;更換一種數(shù)據(jù)源的存儲和獲取方式,只需要修改對應(yīng)層的代碼即可,領(lǐng)域模型層保持了穩(wěn)定。
2.2.2 時序圖
整個系統(tǒng)的時序圖如下:
Sequence Diagram?
系統(tǒng)啟動的時候,在Global.asax中創(chuàng)建了一個自定義了Controller工廠類,應(yīng)用程序?qū)⑵浔4嬖诒镜乇銉煞N,當(dāng)頁面請求進來的時候,程序出發(fā)該工廠類的CreateController方法,并查找web.config中的數(shù)據(jù)庫連接字符串,將其傳遞給新的SqlProductRepository實例,然后將SqlProductRepository實例注入到HomeControll中,并返回。
然后應(yīng)用調(diào)用HomeController的實例方法Index來創(chuàng)建新的ProductService類,并通過構(gòu)造函數(shù)傳入SqlProductRepository。ProductService的GetFeaturedProducts 方法代理給SqlProductRepository實例去實現(xiàn)。
最后,返回填充好了FeaturedProductViewModel的ViewResult對象給頁面,然后MVC進行合適的展現(xiàn)。
2.2.3 新的結(jié)構(gòu)
在1.1的實現(xiàn)中,采用了三層架構(gòu),在改進后的實現(xiàn)中,在UI層和領(lǐng)域模型層中加入了一個表現(xiàn)模型(presentation model)層。如下圖:
presentation model layer
?
將Controllers和ViewModel從表現(xiàn)層移到了表現(xiàn)模型層,僅僅將視圖(.aspx和.ascx文件)和聚合根對象(Composition Root)保留在了表現(xiàn)層中。之所以這樣處理,是可以使得盡可能的使得表現(xiàn)層能夠可配置而其他部分盡可能的可以保持不變。
3. 結(jié)語
一不小心我們就編寫出了緊耦合的代碼,有時候以為分層了就可以解決這一問題,但是大多數(shù)的時候,都沒有正確的實現(xiàn)分層。之所以容易寫出緊耦合的代碼有一個原因是因為編程語言或者開發(fā)環(huán)境允許我們只要需要一個新的實例對象,就可以使用new關(guān)鍵字來實例化一個。如果我們需要添加依賴,Visual Studio有些時候可以自動幫我們添加引用。這使得我們很容易就犯錯,使用new關(guān)鍵字,就可能會引入以來;添加引用就會產(chǎn)生依賴。
減少new引入的依賴及緊耦合最好的方式是使用構(gòu)造函數(shù)注入依賴這種設(shè)計模式:即如果我們需要一個依賴的實例,通過構(gòu)造函數(shù)注入。在第二個部分的實現(xiàn)演示了如何針對抽象而不是具體編程。
構(gòu)造函數(shù)注入是反轉(zhuǎn)控制的一個例子,因為我們反轉(zhuǎn)了對依賴的控制。不是使用new關(guān)鍵字創(chuàng)建一個實例,而是將這種行為委托給了第三方實現(xiàn)。
希望本文能夠給大家了解如何真正實現(xiàn)三層架構(gòu),編寫松散耦合,可維護,可測試性的代碼提供一些幫助。
========
理解依賴注入(IOC)和學(xué)習(xí)Unity
http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.htmlIOC:英文全稱:Inversion of Control,中文名稱:控制反轉(zhuǎn),它還有個名字叫依賴注入(Dependency Injection)。
作用:將各層的對象以松耦合的方式組織在一起,解耦,各層對象的調(diào)用完全面向接口。當(dāng)系統(tǒng)重構(gòu)的時候,代碼的改寫量將大大減少。
理解依賴注入:
? ? 當(dāng)一個類的實例需要另一個類的實例協(xié)助時,在傳統(tǒng)的程序設(shè)計過程中,通常有調(diào)用者來創(chuàng)建被調(diào)用者的實例。然而采用依賴注入的方式,創(chuàng)建被調(diào)用者的工作不再由調(diào)用者來完成,因此叫控制反轉(zhuǎn),創(chuàng)建被調(diào)用者的實例的工作由IOC容器來完成,然后注入調(diào)用者,因此也稱為依賴注入。
舉個有意思的例子(來源于互聯(lián)網(wǎng))
假如我們要設(shè)計一個Girl和一個Boy類,其中Girl有Kiss方法,即Girl想要Kiss一個Boy,首先問題是Girl如何認識Boy?
? ? 在我們中國常見的MM認識GG的方式有以下幾種:
? ? A 青梅竹馬 ? ?B 親友介紹 ? C 父母包辦
? ? 哪一種是最好的?
1.青梅竹馬:很久很久以前,有個有錢的地主家的一閨女叫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對Jimmy的依賴性非常強,緊耦合。
2.親友介紹:經(jīng)常Kiss同一個人令Lily有些厭惡了,她想嘗試新人,于是與Jimmy分手了,通過親朋好友(中間人)來介紹
public class Lily{ ?
? ? ? ? public Boy boy; ??
??
? ? ? ? public Girl() ?
? ? ? ? { ?
? ? ? ? ? ? boy=BoyFactory.createBoy(); ?
? ? ? ? } ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? boy.Kiss(); ?
? ? ? ? } ?
? ? } ?
親友介紹,固然是好。如果不滿意,盡管另外換一個好了。但是,親友BoyFactory經(jīng)常是以Singleton的形式出現(xiàn),不然就是,存在于Globals,無處不在,無處不能。實在是太繁瑣了一點,不夠靈活。我為什么一定要這個親友摻和進來呢?為什么一定要付給她介紹費呢?萬一最好的朋友愛上了我的男朋友呢?
?
3.父母包辦:一切交給父母,自己不用非吹灰之力,Lily在家只Kiss
public class Lily{ ?
? ? ? ? public Boy boy; ??
? ? ? ? public Girl(Boy boy) ?
? ? ? ? { ?
? ? ? ? ? ? this.boy=boy; ?
? ? ? ? } ?
? ? ? ? public void Kiss() ?
? ? ? ? { ?
? ? ? ? ? ? this.boy.Kiss(); ?
? ? ? ? } ?
? ? } ?
?
?
Well,這是對Girl最好的方法,只要想辦法賄賂了Girl的父母,并把Boy交給他。那么我們就可以輕松的和Girl來Kiss了。看來幾千年傳統(tǒng)的父母之命還真是有用哦。至少Boy和Girl不用自己瞎忙乎了。這就是IOC,將對象的創(chuàng)建和獲取提取到外部。由外部容器提供需要的組件。
在設(shè)計模式中我們應(yīng)該還知道依賴倒轉(zhuǎn)原則,應(yīng)是面向接口編程而不是面向功能實現(xiàn),好處是:多實現(xiàn)可以任意切換,我們的Boy應(yīng)該是實現(xiàn)Kissable接口。這樣一旦Girl不想kiss可惡的Boy的話,還可以kiss可愛的kitten和慈祥的grandmother
好在.net中微軟有一個輕量級的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(); ?
? ? ? ? ? ? //注冊映射 ?
? ? ? ? ? ? container.RegisterType<IKiss, Boy>(); ?
? ? ? ? ? ? //得到Boy的實例 ?
? ? ? ? ? ? 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"); ?
? ? ? ? } ?
? ? } ?
} ?
如果采用配置文件注冊的話
<?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="命名空間.實現(xiàn)類型1,命名空間" /> ?
? ? ? ? <register type="命名空間.接口類型2,命名空間" mapTo="命名空間.實現(xiàn)類型2,命名空間" /> ?
? ? ? </container> ?
? ? </containers> ?
? </unity> ?
</configuration> ?
配置的后臺代碼:
UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) ?
? ? ? ? ? ? as UnityConfigurationSection; ?
configuration.Configure(container, "defaultContainer"); ?
可以通過方法ResolveAll來得到所有注冊對象的實例:
var Instances = container.Resolve<IKiss>();
Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴注入劃分為三種形式,即構(gòu)造器注入、屬性(設(shè)置)注入和接口注入,習(xí)慣將其劃分為一種(類型)匹配和三種注入:
類型匹配(Type Matching):雖然我們通過接口(或者抽象類)來進行服務(wù)調(diào)用,但是服務(wù)本身還是實現(xiàn)在某個具體的服務(wù)類型中,這就需要某個類型注冊機制來解決服務(wù)接口和服務(wù)類型之間的匹配關(guān)系;
構(gòu)造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調(diào)用適合的構(gòu)造函數(shù)以創(chuàng)建依賴的對象。如果被選擇的構(gòu)造函數(shù)具有相應(yīng)的參數(shù),IoC容器在調(diào)用構(gòu)造函數(shù)之前解析注冊的依賴關(guān)系并自行獲得相應(yīng)參數(shù)對象;
屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創(chuàng)建之后,IoC容器會自動初始化該屬性;
方法注入(Method Injection):如果被依賴對象需要調(diào)用某個方法進行相應(yīng)的初始化,在該對象創(chuàng)建之后,IoC容器會自動調(diào)用該方法。
?
?
我們創(chuàng)建一個控制臺程序,定義如下幾個接口(IA、IB、IC和ID)和它們各自的實現(xiàn)類(A、B、C、D)。在類型A中定義了3個屬性B、C和D,其類型分別為接口IB、IC和ID。其中屬性B在構(gòu)在函數(shù)中被初始化,以為著它會以構(gòu)造器注入的方式被初始化;屬性C上應(yīng)用了DependencyAttribute特性,意味著這是一個需要以屬性注入方式被初始化的依賴屬性;屬性D則通過方法Initialize初始化,該方法上應(yīng)用了特性InjectionMethodAttribute,意味著這是一個注入方法在A對象被IoC容器創(chuà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)用添加一個配置文件,并定義如下一段關(guān)于Unity的配置。這段配置定義了一個名稱為defaultContainer的Unity容器,并在其中完成了上面定義的接口和對應(yīng)實現(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)建一個代表IoC容器的UnityContainer對象,并加載配置信息對其進行初始化。然后調(diào)用它的泛型的Resolve方法創(chuàng)建一個實現(xiàn)了泛型接口IA的對象。最后將返回對象轉(zhuǎn)變成類型A,并檢驗其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>方法返回的是一個類型為A的對象,該對象的三個屬性被進行了有效的初始化。這個簡單的程序分別體現(xiàn)了接口注入(通過相應(yīng)的接口根據(jù)配置解析出相應(yīng)的實現(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ā)中,程序員在某個類中需要依賴其它類的方法,則通常是new一個依賴類再調(diào)用類實例的方法,這種開發(fā)存在的問題是new的類實例不好統(tǒng)一管理,spring提出了依賴注入的思想,即依賴類不由程序員實例化,而是通過spring容器幫我們new指定實例并且將實例注入到需要該對象的類中。依賴注入的另一種說法是“控制反轉(zhuǎn)”,通俗的理解是:平常我們new一個實例,這個實例的控制權(quán)是我們程序員,而控制反轉(zhuǎn)是指new實例工作不由我們程序員來做而是交給spring容器來做。
spring有多種依賴注入的形式,下面僅介紹spring通過xml進行IOC配置的方式:
Set注入
這是最簡單的注入方式,假設(shè)有一個SpringAction,類中需要實例化一個SpringDao對象,那么就可以定義一個private的SpringDao成員變量,然后創(chuàng)建SpringDao的set方法(這是ioc的注入入口):
Java代碼 ?收藏代碼
package com.bless.springdemo.action; ?
public class SpringAction { ?
? ? ? ? //注入對象springDao ?
? ? private SpringDao springDao; ?
? ? ? ? //一定要寫被注入對象的set方法 ?
? ? ? ? public void setSpringDao(SpringDao springDao) { ?
? ? ? ? this.springDao = springDao; ?
? ? } ?
??
? ? ? ? public void ok(){ ?
? ? ? ? springDao.ok(); ?
? ? } ?
} ?
隨后編寫spring的xml文件,<bean>中的name屬性是class屬性的一個別名,class屬性指類的全名,因為在SpringAction中有一個公共屬性Springdao,所以要在<bean>標(biāo)簽中創(chuàng)建一個<property>標(biāo)簽指定SpringDao。<property>標(biāo)簽中的name就是SpringAction類中的SpringDao屬性名,ref指下面<bean name="springDao"...>,這樣其實是spring將SpringDaoImpl對象實例化并且調(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)建了兩個成員變量SpringDao和User,但是并未設(shè)置對象的set方法,所以就不能支持第一種注入方式,這里的注入方式是在SpringAction的構(gòu)造函數(shù)中注入,也就是說在創(chuàng)建SpringAction對象時要將SpringDao和User兩個參數(shù)值傳進來:
Java代碼 ?收藏代碼
public class SpringAction { ?
? ? //注入對象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ù)的不確定性,你可能會遇到構(gòu)造方法傳入的兩參數(shù)都是同類型的,為了分清哪個該賦對應(yīng)值,則需要進行一些小處理:
下面是設(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)工廠的方法來獲取自己需要的對象,為了讓spring管理所有對象,我們不能直接通過"工程類.靜態(tài)方法()"來獲取對象,而是依然通過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)鍵類,這里我需要注入一個FactoryDao對象,這里看起來跟第一種注入一模一樣,但是看隨后的xml會發(fā)現(xiàn)有很大差別:
Java代碼 ?收藏代碼
?public class SpringAction { ?
? ? ? ? //注入對象 ?
? ? private FactoryDao staticFactoryDao; ?
? ? ??
? ? public void staticFactoryOk(){ ?
? ? ? ? staticFactoryDao.saveFactory(); ?
? ? } ?
? ? //注入對象的set方法 ?
? ? public void setStaticFactoryDao(FactoryDao staticFactoryDao) { ?
? ? ? ? this.staticFactoryDao = staticFactoryDao; ?
? ? } ?
} ?
?
Spring的IOC配置文件,注意看<bean name="staticFactoryDao">指向的class并不是FactoryDao的實現(xiàn)類,而是指向靜態(tài)工廠DaoFactory,并且配置 factory-method="getStaticFactoryDaoImpl"指定調(diào)用哪個工廠方法:
Xml代碼 ?收藏代碼
<!--配置bean,配置后該類由spring管理--> ?
? ? <bean name="springAction" class="com.bless.springdemo.action.SpringAction" > ?
? ? ? ? <!--(3)使用靜態(tài)工廠的方法注入對象,對應(yīng)下面的配置文件(3)--> ?
? ? ? ? <property name="staticFactoryDao" ref="staticFactoryDao"></property> ?
? ? ? ? ? ? ? ? </property> ?
? ? </bean> ?
? ? <!--(3)此處獲取對象的方式是從工廠類中獲取靜態(tài)方法--> ?
? ? <bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean> ?
? ? ?
實例工廠的方法注入
實例工廠的意思是獲取對象實例的方法不是靜態(tài)的,所以你需要首先new工廠類,再調(diào)用普通的實例方法:
Java代碼 ?收藏代碼
public class DaoFactory { ?
? ? //實例工廠 ?
? ? public FactoryDao getFactoryDaoImpl(){ ?
? ? ? ? return new FactoryDaoImpl(); ?
? ? } ?
} ?
那么下面這個類沒什么說的,跟前面也很相似,但是我們需要通過實例工廠類創(chuàng)建FactoryDao對象:
Java代碼 ?收藏代碼
public class SpringAction { ?
? ? //注入對象 ?
? ? 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)使用實例工廠的方法注入對象,對應(yīng)下面的配置文件(4)--> ?
? ? ? ? <property name="factoryDao" ref="factoryDao"></property> ?
? ? </bean> ?
? ? ??
? ? <!--(4)此處獲取對象的方式是從工廠類中獲取實例方法--> ?
? ? <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)種,多謝多練就會非常熟練。
? ? ? ? 另外注意:通過Spring創(chuàng)建的對象默認是單例的,如果需要創(chuàng)建多實例對象可以在<bean>標(biāo)簽后面添加一個屬性:
Java代碼 ?收藏代碼
<bean name="..." class="..." scope="prototype">?
========
深度理解依賴注入
http://kb.cnblogs.com/page/45266/4/摘要:提到依賴注入,大家都會想到老馬那篇經(jīng)典的文章。其實,本文就是相當(dāng)于對那篇文章的解讀。所以,如果您對原文已經(jīng)有了非常深刻的理解,完全不需要再看此文;但是,如果您和筆者一樣,以前曾經(jīng)看過,似乎看懂了,但似乎又沒抓到什么要領(lǐng),不妨看看筆者這個解讀,也許對您理解原文有一定幫助。
[1] 依賴在哪里
[2] DI的實現(xiàn)方式
[3] Setter Injection
[4] 除了DI,還有Service Locator
1.依賴在哪里
? ?老馬舉了一個小例子,是開發(fā)一個電影列舉器(MovieList),這個電影列舉器需要使用一個電影查找器(MovieFinder)提供的服務(wù),偽碼如下:
?1/*服務(wù)的接口*/
?2public interface MovieFinder {
?3 ? ?ArrayList findAll();
?4}
?5
?6/*服務(wù)的消費者*/
?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 ? ?/*消費者內(nèi)部包含一個將指向具體服務(wù)類型的實體對象*/
19 ? ?private MovieFinder finder;
20 ? ?/*消費者需要在某一個時刻去實例化具體的服務(wù)。這是我們要解耦的關(guān)鍵所在,
21 ? ? *因為這樣的處理方式造成了服務(wù)消費者和服務(wù)提供者的強耦合關(guān)系(這種耦合是在編譯期就確定下來的)。
22 ? ? **/
23 ? ?public MovieLister() {
24 ? ? ? ?finder = new ColonDelimitedMovieFinder("movies1.txt");
25 ? ?}
26}
從上面代碼的注釋中可以看到,MovieLister和ColonDelimitedMovieFinder(這可以使任意一個實現(xiàn)了MovieFinder接口的類型)之間存在強耦合關(guān)系,如下圖所示:
圖1
這使得MovieList很難作為一個成熟的組件去發(fā)布,因為在不同的應(yīng)用環(huán)境中(包括同一套軟件系統(tǒng)被不同用戶使用的時候),它所要依賴的電影查找器可能是千差萬別的。所以,為了能實現(xiàn)真正的基于組件的開發(fā),必須有一種機制能同時滿足下面兩個要求:
?(1)解除MovieList對具體MoveFinder類型的強依賴(編譯期依賴)。
?(2)在運行的時候為MovieList提供正確的MovieFinder類型的實例。
? ?換句話說,就是在運行的時候才產(chǎn)生MovieList和MovieFinder之間的依賴關(guān)系(把這種依賴關(guān)系在一個合適的時候“注入”運行時),這恐怕就是Dependency Injection這個術(shù)語的由來。再換句話說,我們提到過解除強依賴,這并不是說MovieList和MovieFinder之間的依賴關(guān)系不存在了,事實上MovieList無論如何也需要某類MovieFinder提供的服務(wù),我們只是把這種依賴的建立時間推后了,從編譯器推遲到運行時了。
? ?依賴關(guān)系在OO程序中是廣泛存在的,只要A類型中用到了B類型實例,A就依賴于B。前面筆者談到的內(nèi)容是把概念抽象到了服務(wù)使用者和服務(wù)提供者的角度,這也符合現(xiàn)在SOA的設(shè)計思路。從另一種抽象方式上來看,可以把MovieList看成我們要構(gòu)建的主系統(tǒng),而MovieFinder是系統(tǒng)中的plugin,主系統(tǒng)并不強依賴于任何一個插件,但一旦插件被加載,主系統(tǒng)就應(yīng)該可以準(zhǔn)確調(diào)用適當(dāng)插件的功能。
? ?其實不管是面向服務(wù)的編程模式,還是基于插件的框架式編程,為了實現(xiàn)松耦合(服務(wù)調(diào)用者和提供者之間的or框架和插件之間的),都需要在必要的位置實現(xiàn)面向接口編程,在此基礎(chǔ)之上,還應(yīng)該有一種方便的機制實現(xiàn)具體類型之間的運行時綁定,這就是DI所要解決的問題。
2.DI的實現(xiàn)方式
? ?和上面的圖1對應(yīng)的是,如果我們的系統(tǒng)實現(xiàn)了依賴注入,組件間的依賴關(guān)系就變成了圖2:
圖2
說白了,就是要提供一個容器,由容器來完成(1)具體ServiceProvider的創(chuàng)建(2)ServiceUser和ServiceProvider的運行時綁定。下面我們就依次來看一下三種典型的依賴注入方式的實現(xiàn)。特別要說明的是,要理解依賴注入的機制,關(guān)鍵是理解容器的實現(xiàn)方式。本文后面給出的容器參考實現(xiàn),均為黃忠成老師的代碼,筆者僅在其中加上了一些關(guān)鍵注釋而已。
2.1 Constructor Injection(構(gòu)造器注入)
?我們可以看到,在整個依賴注入的數(shù)據(jù)結(jié)構(gòu)中,涉及到的重要的類型就是ServiceUser, ServiceProvider和Assembler三者,而這里所說的構(gòu)造器,指的是ServiceUser的構(gòu)造器。也就是說,在構(gòu)造ServiceUser實例的時候,才把真正的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的已完成依賴注入實例,
?5 ? ?//其中用到的實例參數(shù)和類型參數(shù)一般是從配置檔中讀取的,這里是個簡單的寫法。
?6 ? ?//所有的依賴注入方法都會有類似的容器初始化過程,本文在后面的小節(jié)中就不再重復(fù)這一段代碼了。
?7 ? ?Parameter[] finderParams = ?{new ConstantParameter("movies1.txt")};
?8 ? ?pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
?9 ? ?pico.registerComponentImplementation(MovieLister.class);
10 ? ?//至此,容器里面裝入了兩個類型,其中沒給出構(gòu)造參數(shù)的那一個(MovieLister)將依靠其在構(gòu)造器中定義的傳入?yún)?shù)類型,在容器中
11 ? ?//進行查找,找到一個類型匹配項即可進行構(gòu)造初始化。
12 ? ?return pico;
13}
需要在強調(diào)一下的是,依賴并未消失,只是延后到了容器被構(gòu)建的時刻。所以正如圖2中您已經(jīng)看到的,容器本身(更準(zhǔn)確的說,是一個容器運行實例的構(gòu)建過程)對ServiceUser和ServiceProvoder都是存在依賴關(guān)系的。所以,在這樣的體系結(jié)構(gòu)里,ServiceUser、ServiceProvider和容器都是穩(wěn)定的,互相之間也沒有任何依賴關(guān)系;所有的依賴關(guān)系、所有的變化都被封裝進了容器實例的創(chuàng)建過程里,符合我們對服務(wù)應(yīng)用的理解。而且,在實際開發(fā)中我們一般會采用配置文件來輔助容器實例的創(chuàng)建,將這種變化性排斥到編譯期之外。
? ?即使還沒給出后面的代碼,你也一定猜得到,這個container類一定有一個GetInstance(Type t)這樣的方法,這個方法會為我們返回一個已經(jīng)注入完畢的MovieLister。 一個簡單的應(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)鍵的就是對pico.getComponentInstance的調(diào)用。Assembler會在這個時候調(diào)用MovieLister的構(gòu)造器,構(gòu)造器的參數(shù)就是當(dāng)時通過pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)設(shè)置進去的實際的ServiceProvider--ColonMovieFinder。下面請看這個容器的參考代碼:
2.2 Setter Injection(設(shè)值注入)
? ?這種注入方式和構(gòu)造注入實在很類似,唯一的區(qū)別就是前者在構(gòu)造函數(shù)的調(diào)用過程中進行注入,而它是通過給屬性賦值來進行注入。無怪乎PicoContainer和Spring都是同時支持這兩種注入方式。Spring對通過XML進行配置有比較好的支持,也使得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è)值注入的容器參考實現(xiàn),大家可以和構(gòu)造器注入的容器對照起來看,里面的差別很小,主要的差別就在于,在獲取對象實例(GetInstance)的時候,前者是通過反射得到待創(chuàng)建類型的構(gòu)造器信息,然后根據(jù)構(gòu)造器傳入?yún)?shù)的類型在容器中進行查找,并構(gòu)造出合適的實例;而后者是通過反射得到待創(chuàng)建類型的所有屬性,然后根據(jù)屬性的類型在容器中查找相應(yīng)類型的實例。
設(shè)值注入的容器實現(xiàn)偽碼
2.3 Interface Injection (接口注入)
? ?這是筆者認為最不夠優(yōu)雅的一種依賴注入方式。要實現(xiàn)接口注入,首先ServiceProvider要給出一個接口定義:
1public interface InjectFinder {
2 ? ?void injectFinder(MovieFinder finder);
3}
接下來,ServiceUser必須實現(xiàn)這個接口:
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來專門負責(zé)提供具體的ServiceProvider。當(dāng)然,這樣的話ServiceUser不僅要依賴于服務(wù)的接口,還依賴于ServiceContract。仍然是最早提到過的電影列舉器的例子,如果使用Service Locator來解除依賴的話,整個依賴關(guān)系應(yīng)當(dāng)如下圖所示:
圖3
用起來也很簡單,在一個適當(dāng)?shù)奈恢?#xff08;比如在一組相關(guān)服務(wù)即將被調(diào)用之前)對ServiceLocator進行初始化,用到的時候就直接用ServiceLocator返回ServiceProvider實例:
?
1//服務(wù)定位器的初始化
2ServiceLocator locator = new ServiceLocator();
3locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));
4ServiceLocator.load(locator);
5//服務(wù)定義器的使用
6//其實這個使用方式體現(xiàn)了服務(wù)定位器和依賴注入模式的最大差別:ServiceUser需要顯示的調(diào)用ServiceLocator,從而獲取自己需要的服務(wù)對象;
7//而依賴注入則是隱式的由容器完成了這一切。
8MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");
9
正因為上面提到過的ServiceUser對ServiceLocator的依賴性,從提高模塊的獨立性(比如說,你可能把你構(gòu)造的ServiceUser或者ServiceProvider給第三方使用)上來說,依賴注入可能更好一些,這恐怕也是為什么大多數(shù)的IOC框架都選用了DI的原因。ServiceLocator最大的優(yōu)點可能在于實現(xiàn)起來非常簡單,如果您開發(fā)的應(yīng)用沒有復(fù)雜到需要采用一個IOC框架的程度,也許您可以試著采用它。
3.廣義的服務(wù)
? ?文中很多地方提到服務(wù)使用者(ServiceUser)和服務(wù)提供者(ServiceProvider)的概念,這里的“服務(wù)”是一種非常廣義的概念,在語法層面就是指最普通的依賴關(guān)系(類型A中有一個B類型的變量,則A依賴于B)。如果您把服務(wù)理解為WCF或者Web Service中的那種服務(wù)概念,您會發(fā)現(xiàn)上面所說的所有技術(shù)手段都是沒有意義的。以WCF而論,其客戶端和服務(wù)器端本就是依賴于Contract的松耦合關(guān)系,其實這也從另一個角度說明了SOA應(yīng)用的優(yōu)勢所在。
========
總結(jié)
- 上一篇: VC++ .Net 实例学习
- 下一篇: windbg查看设备栈设备树学习总结