浅谈依赖注入
最近幾天在看一本名為Dependency Injection in .NET?的書(shū),主要講了什么是依賴注入,使用依賴注入的優(yōu)點(diǎn),以及.NET平臺(tái)上依賴注入的各種框架和用法。在這本書(shū)的開(kāi)頭,講述了軟件工程中的一個(gè)重要的理念就是關(guān)注分離(Separation of concern,?SoC)。依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開(kāi)發(fā)出松散耦合(loose coupled)、可維護(hù)、可測(cè)試的代碼和程序。這條原則的做法是大家熟知的面向接口,或者說(shuō)是面向抽象編程。
關(guān)于什么是依賴注入,在Stack Overflow上面有一個(gè)問(wèn)題,如何向一個(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ū)ο蟪绦蜷_(kāi)發(fā)中就是:高層類(5歲小孩)應(yīng)該依賴底層基礎(chǔ)設(shè)施(家長(zhǎng))來(lái)提供必要的服務(wù)。
編寫(xiě)松耦合的代碼說(shuō)起來(lái)很簡(jiǎn)單,但是實(shí)際上寫(xiě)著寫(xiě)著就變成了緊耦合。
使用例子來(lái)說(shuō)明可能更簡(jiǎn)潔明了,首先來(lái)看看什么樣的代碼是緊耦合。
?
1 不好的實(shí)現(xiàn)
編寫(xiě)松耦合代碼的第一步,可能大家都熟悉,那就是對(duì)系統(tǒng)分層。比如下面的經(jīng)典的三層架構(gòu)。
分完層和實(shí)現(xiàn)好是兩件事情,并不是說(shuō)分好層之后就能夠松耦合了。
1.1 緊耦合的代碼
有很多種方式來(lái)設(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)站,要列出商品列表,如下:
下面就具體來(lái)演示通常的做法,是如何一步一步把代碼寫(xiě)出緊耦合的。
1.1.1 數(shù)據(jù)訪問(wèn)層
要實(shí)現(xiàn)商品列表這一功能,首先要編寫(xiě)數(shù)據(jù)訪問(wèn)層,需要設(shè)計(jì)數(shù)據(jù)庫(kù)及表,在SQLServer中設(shè)計(jì)的數(shù)據(jù)庫(kù)表Product結(jié)構(gòu)如下:
表設(shè)計(jì)好之后,就可以開(kāi)始寫(xiě)代碼了。在Visual Studio 中,新建一個(gè)名為DataAccessLayer的工程,添加一個(gè)ADO.NET Entity Data Model,此時(shí)Visual Studio的向?qū)?huì)自動(dòng)幫我們生成Product實(shí)體和ObjectContext DB操作上下文。這樣我們的 Data Access Layer就寫(xiě)好了。
1.1.2 業(yè)務(wù)邏輯層
表現(xiàn)層實(shí)際上可以直接訪問(wèn)數(shù)據(jù)訪問(wèn)層,通過(guò)ObjectContext 獲取Product 列表。但是大多數(shù)情況下,我們不是直接把DB里面的數(shù)據(jù)展現(xiàn)出來(lái),而是需要對(duì)數(shù)據(jù)進(jìn)行處理,比如對(duì)會(huì)員,需要對(duì)某些商品的價(jià)格打折。這樣我們就需要業(yè)務(wù)邏輯層,來(lái)處理這些與具體業(yè)務(wù)邏輯相關(guān)的事情。
新建一個(gè)類庫(kù),命名為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 頁(yè)面的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)出來(lái):
<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)”我們的代碼寫(xiě)好了,并且也達(dá)到了要求。整個(gè)項(xiàng)目的結(jié)構(gòu)如下圖:
?
這應(yīng)該是我們通常經(jīng)常寫(xiě)的所謂的三層架構(gòu)。在Visual Studio中,三層之間的依賴可以通過(guò)項(xiàng)目引用表現(xiàn)出來(lái)。
1.2.1 依賴關(guān)系圖
現(xiàn)在我們來(lái)分析一下,這三層之間的依賴關(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)系是這樣的:
1.2.2 耦合性分析
使用三層結(jié)構(gòu)的主要目的是分離關(guān)注點(diǎn),當(dāng)然還有一個(gè)原因是可測(cè)試性。我們應(yīng)該將領(lǐng)域模型從數(shù)據(jù)訪問(wèn)層和表現(xiàn)層中分離出來(lái),這樣這兩個(gè)層的變化才不會(huì)污染領(lǐng)域模型。在大的系統(tǒng)中,這點(diǎn)很重要,這樣才能將系統(tǒng)中的不同部分隔離開(kāi)來(lái)。
現(xiàn)在來(lái)看之前的實(shí)現(xiàn)中,有沒(méi)有模塊性,有沒(méi)有那個(gè)模塊可以隔離出來(lái)呢。現(xiàn)在添加幾個(gè)新的case來(lái)看,系統(tǒng)是否能夠響應(yīng)這些需求:
添加新的用戶界面
除了WebForm用戶之外,可能還需要一個(gè)WinForm的界面,現(xiàn)在我們能否復(fù)用領(lǐng)域?qū)雍蛿?shù)據(jù)訪問(wèn)層呢?從依賴圖中可以看到,沒(méi)有任何一個(gè)模塊會(huì)依賴表現(xiàn)層,因此很容易實(shí)現(xiàn)這一點(diǎn)變化。我們只需要?jiǎng)?chuàng)建一個(gè)WPF的富客戶端就可以?,F(xiàn)在整個(gè)系統(tǒng)的依賴圖如下:
更換新的數(shù)據(jù)源
可能過(guò)了一段時(shí)間,需要把整個(gè)系統(tǒng)部署到云上,要使用其他的數(shù)據(jù)存儲(chǔ)技術(shù),比如Azure Table Storage Service?,F(xiàn)在,整個(gè)訪問(wèn)數(shù)據(jù)的協(xié)議發(fā)生了變化,訪問(wèn)Azure Table Storage Service的方式是Http協(xié)議,而之前的大多數(shù).NET 訪問(wèn)數(shù)據(jù)的方式都是基于ADO.NET 的方式。并且數(shù)據(jù)源的保存方式也發(fā)生了改變,之前是關(guān)系型數(shù)據(jù)庫(kù),現(xiàn)在變成了key-value型數(shù)據(jù)庫(kù)。
由上面的依賴關(guān)系圖可以看出,所有的層都依賴了數(shù)據(jù)訪問(wèn)層,如果修改數(shù)據(jù)訪問(wèn)層,則領(lǐng)域邏輯層,和表現(xiàn)層都需要進(jìn)行相應(yīng)的修改。
1.2.3 問(wèn)題
除了上面的各層之間耦合下過(guò)強(qiáng)之外,代碼中還有其他問(wèn)題。
- 領(lǐng)域模型似乎都寫(xiě)到了數(shù)據(jù)訪問(wèn)層中。所以領(lǐng)域模型看起來(lái)依賴了數(shù)據(jù)訪問(wèn)層。在數(shù)據(jù)訪問(wèn)層中定義了名為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ù)訪問(wèn)層,所以也會(huì)依賴在web.config 中配置的數(shù)據(jù)庫(kù)連接字符串等信息。這使得,整個(gè)業(yè)務(wù)邏輯層也需要依賴這些配置才能正常運(yùn)行。
- 在View中,包含了太多了函數(shù)性功能。他執(zhí)行了強(qiáng)制類型轉(zhuǎn)換,字符串格式化等操作,這些功能應(yīng)該是在界面顯示得模型中完成。
上面可能是我們大多數(shù)寫(xiě)代碼時(shí)候的實(shí)現(xiàn), UI界面層去依賴了數(shù)據(jù)訪問(wèn)層,有時(shí)候偷懶就直接引用了這一層,因?yàn)閷?shí)體定義在里面了。業(yè)務(wù)邏輯層也是依賴數(shù)據(jù)訪問(wèn)層,直接在業(yè)務(wù)邏輯里面使用了數(shù)據(jù)訪問(wèn)層里面的實(shí)體。這樣使得整個(gè)系統(tǒng)緊耦合,并且可測(cè)試性差。那現(xiàn)在我們看看,如何修改這樣一個(gè)系統(tǒng),使之達(dá)到松散耦合,從而提高可測(cè)試性呢?
?
2 較好的實(shí)現(xiàn)
依賴注入能夠較好的解決上面出現(xiàn)的問(wèn)題,現(xiàn)在可以使用這一思想來(lái)重新實(shí)現(xiàn)前面的系統(tǒng)。之所以重新實(shí)現(xiàn)是因?yàn)?#xff0c;前面的實(shí)現(xiàn)在一開(kāi)始的似乎就沒(méi)有考慮到擴(kuò)展性和松耦合,使用重構(gòu)的方式很難達(dá)到理想的效果。對(duì)于小的系統(tǒng)來(lái)說(shuō)可能還可以,但是對(duì)于一個(gè)大型的系統(tǒng),應(yīng)該是比較困難的。
在寫(xiě)代碼的時(shí)候,要管理好依賴性,在前面的實(shí)現(xiàn)這種,代碼直接控制了依賴性:當(dāng)ProductService需要一個(gè)ObjectContext類的似乎,直接new了一個(gè),當(dāng)HomeController需要一個(gè)ProductService的時(shí)候,直接new了一個(gè),這樣看起來(lái)很酷很方便,實(shí)際上使得整個(gè)系統(tǒng)具有很大的局限性,變得緊耦合。new 操作實(shí)際上就引入了依賴, 控制反轉(zhuǎn)這種思想就是要使的我們比較好的管理依賴。
2.1 松耦合的代碼
2.1.1 表現(xiàn)層
首先從表現(xiàn)層來(lái)分析,表現(xiàn)層主要是用來(lái)對(duì)數(shù)據(jù)進(jìn)行展現(xiàn),不應(yīng)該包含過(guò)多的邏輯。在Index的View頁(yè)面中,代碼希望可以寫(xiě)成這樣
<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ù)展示的字段。
現(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è)類庫(kù),這里面包含POCOs和一些抽象類型。POCOs用來(lái)對(duì)領(lǐng)域建模,抽象類型提供抽象作為到達(dá)領(lǐng)域模型的入口。依賴注入的原則是面向接口而不是具體的類編程,使得我們可以替換具體實(shí)現(xiàn)。
現(xiàn)在我們需要為表現(xiàn)層提供數(shù)據(jù)。因此用戶界面層需要引用領(lǐng)域模型層。對(duì)數(shù)據(jù)訪問(wèn)層的簡(jiǎn)單抽象可以采用Patterns of Enterprise Application Architecture一書(shū)中講到的Repository模式。因此定義一個(gè)ProductRepository抽象類,注意是抽象類,在領(lǐng)域模型庫(kù)中。它定義了一個(gè)獲取所有特價(jià)商品的抽象方法:
public abstract class ProductRepository {public abstract IEnumerable<Product> GetFeaturedProducts(); }這個(gè)方法的Product類中只定義了商品的基本信息比如名稱和單價(jià)。整個(gè)關(guān)系圖如下:
現(xiàn)在來(lái)看表現(xiàn)層,HomeController中的Index方法應(yīng)該要使用ProductService實(shí)例類來(lái)獲取商品列表,執(zhí)行價(jià)格打折,并且把Product類似轉(zhuǎn)化為ProductViewModel實(shí)例,并將該實(shí)例加入到FeaturesProductsViewModel中。因?yàn)镻roductService有一個(gè)帶有類型為ProductReposity抽象類的構(gòu)造函數(shù),所以這里可以通過(guò)構(gòu)造函數(shù)注入實(shí)現(xiàn)了ProductReposity抽象類的實(shí)例。這里和之前的最大區(qū)別是,我們沒(méi)有使用new關(guān)鍵字來(lái)立即new一個(gè)對(duì)象,而是通過(guò)構(gòu)造函數(shù)的方式傳入具體的實(shí)現(xiàn)。
現(xiàn)在來(lái)看表現(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ì)象中,這就是典型的通過(guò)構(gòu)造函數(shù)注入。在Index方法中,獲取數(shù)據(jù)的ProductService類中的主要功能,實(shí)際上是通過(guò)傳入的repository類來(lái)代理完成的。
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 inthis.repository.GetFeaturedProducts()select p.ApplyDiscountFor(user);} }可以看到ProductService也是通過(guò)構(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)]有處理了:
- 沒(méi)有ProductRepository的具體實(shí)現(xiàn),這個(gè)很容易實(shí)現(xiàn),后面放到數(shù)據(jù)訪問(wèn)層里面去處理,我們只需要?jiǎng)?chuàng)建一個(gè)具體的實(shí)現(xiàn)了ProductRepository的數(shù)據(jù)訪問(wèn)類即可。
- 默認(rèn)上,ASP.NET MVC 希望Controller對(duì)象有自己的默認(rèn)構(gòu)造函數(shù),因?yàn)槲覀冊(cè)贖omeController中添加了新的構(gòu)造函數(shù)來(lái)注入依賴,所以MVC框架不知道如何解決創(chuàng)建實(shí)例,因?yàn)橛幸蕾嚒_@個(gè)問(wèn)題可以通過(guò)開(kāi)發(fā)一個(gè)IControllerFactory來(lái)解決,該對(duì)象可以創(chuàng)建一個(gè)具體的ProductRepositry實(shí)例,然后傳給HomeController這里不多講。
現(xiàn)在我們的領(lǐng)域邏輯層已經(jīng)寫(xiě)好了。在該層,我們只操作領(lǐng)域模型對(duì)象,以及.NET BCL 中的基本對(duì)象。模型使用POCOs來(lái)表示,命名為Product。領(lǐng)域模型層必須能夠和外界進(jìn)行交流(database),所以需要一個(gè)抽象類(Repository)來(lái)時(shí)完成這一功能,并且在必要的時(shí)候,可以替換具體實(shí)現(xiàn)。
2.1.3 數(shù)據(jù)訪問(wèn)層
現(xiàn)在我們可以使用LINQ to Entity來(lái)實(shí)現(xiàn)具體的數(shù)據(jù)訪問(wèn)層邏輯了。因?yàn)橐獙?shí)現(xiàn)領(lǐng)域模型的ProductRepository抽象類,所以需要引入領(lǐng)域模型層。注意,這里的依賴變成了數(shù)據(jù)訪問(wèn)層依賴領(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.Productswhere p.IsFeaturedselect p).AsEnumerable();return from p in productsselect p.ToDomainProduct();} }在這里需要注意的是,在領(lǐng)域模型層中,我們定義了一個(gè)名為Product的領(lǐng)域模型,然后再數(shù)據(jù)訪問(wèn)層中Entity Framework幫我們也生成了一個(gè)名為Product的數(shù)據(jù)訪問(wèn)層實(shí)體,他是和db中的Product表一一對(duì)應(yīng)的。所以我們?cè)诜椒ǚ祷氐臅r(shí)候,需要把類型從db中的Product轉(zhuǎn)換為領(lǐng)域模型中的POCOs Product對(duì)象。
Domain Model中的Product是一個(gè)POCOs類型的對(duì)象,他僅僅包含領(lǐng)域模型中需要用到的一些基本字段,DataAccess中的Product對(duì)象是映射到DB中的實(shí)體,它包含數(shù)據(jù)庫(kù)中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)系圖如下:
表現(xiàn)層和數(shù)據(jù)訪問(wèn)層都依賴領(lǐng)域模型層,這樣,在前面的case中,如果我們新添加一個(gè)UI界面;更換一種數(shù)據(jù)源的存儲(chǔ)和獲取方式,只需要修改對(duì)應(yīng)層的代碼即可,領(lǐng)域模型層保持了穩(wěn)定。
2.2.2 時(shí)序圖
整個(gè)系統(tǒng)的時(shí)序圖如下:
系統(tǒng)啟動(dòng)的時(shí)候,在Global.asax中創(chuàng)建了一個(gè)自定義了Controller工廠類,應(yīng)用程序?qū)⑵浔4嬖诒镜乇銉煞N,當(dāng)頁(yè)面請(qǐng)求進(jìn)來(lái)的時(shí)候,程序出發(fā)該工廠類的CreateController方法,并查找web.config中的數(shù)據(jù)庫(kù)連接字符串,將其傳遞給新的SqlProductRepository實(shí)例,然后將SqlProductRepository實(shí)例注入到HomeControll中,并返回。
然后應(yīng)用調(diào)用HomeController的實(shí)例方法Index來(lái)創(chuàng)建新的ProductService類,并通過(guò)構(gòu)造函數(shù)傳入SqlProductRepository。ProductService的GetFeaturedProducts 方法代理給SqlProductRepository實(shí)例去實(shí)現(xiàn)。
最后,返回填充好了FeaturedProductViewModel的ViewResult對(duì)象給頁(yè)面,然后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)層。如下圖:
?
將Controllers和ViewModel從表現(xiàn)層移到了表現(xiàn)模型層,僅僅將視圖(.aspx和.ascx文件)和聚合根對(duì)象(Composition Root)保留在了表現(xiàn)層中。之所以這樣處理,是可以使得盡可能的使得表現(xiàn)層能夠可配置而其他部分盡可能的可以保持不變。
?
3. 結(jié)語(yǔ)
一不小心我們就編寫(xiě)出了緊耦合的代碼,有時(shí)候以為分層了就可以解決這一問(wèn)題,但是大多數(shù)的時(shí)候,都沒(méi)有正確的實(shí)現(xiàn)分層。之所以容易寫(xiě)出緊耦合的代碼有一個(gè)原因是因?yàn)榫幊陶Z(yǔ)言或者開(kāi)發(fā)環(huán)境允許我們只要需要一個(gè)新的實(shí)例對(duì)象,就可以使用new關(guān)鍵字來(lái)實(shí)例化一個(gè)。如果我們需要添加依賴,Visual Studio有些時(shí)候可以自動(dòng)幫我們添加引用。這使得我們很容易就犯錯(cuò),使用new關(guān)鍵字,就可能會(huì)引入以來(lái);添加引用就會(huì)產(chǎn)生依賴。
減少new引入的依賴及緊耦合最好的方式是使用構(gòu)造函數(shù)注入依賴這種設(shè)計(jì)模式:即如果我們需要一個(gè)依賴的實(shí)例,通過(guò)構(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),編寫(xiě)松散耦合,可維護(hù),可測(cè)試性的代碼提供一些幫助。
作者:?yangecnu(yangecnu's Blog on 博客園)?
出處:http://www.cnblogs.com/yangecnu/?
本作品由yangecnu?創(chuàng)作,采用知識(shí)共享署名-非商業(yè)性使用-禁止演繹 2.5 中國(guó)大陸許可協(xié)議進(jìn)行許可。 歡迎轉(zhuǎn)載,但任何轉(zhuǎn)載必須保留完整文章,在顯要地方顯示署名以及原文鏈接。如您有任何疑問(wèn)或者授權(quán)方面的協(xié)商,請(qǐng)?給我留言。
總結(jié)
- 上一篇: 浅谈高性能数据库集群 —— 读写分离
- 下一篇: Redis 面试题 50 问,史上最全