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