日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

浅谈依赖注入

發布時間:2025/3/21 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈依赖注入 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近幾天在看一本名為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 不好的實現


編寫松耦合代碼的第一步,可能大家都熟悉,那就是對系統分層。比如下面的經典的三層架構。

分完層和實現好是兩件事情,并不是說分好層之后就能夠松耦合了。

1.1 緊耦合的代碼

有很多種方式來設計一個靈活的,可維護的復雜應用,但是n層架構是一種大家比較熟悉的方式,這里面的挑戰在于如何正確的實現n層架構。

假設要實現一個很簡單的電子商務網站,要列出商品列表,如下:

下面就具體來演示通常的做法,是如何一步一步把代碼寫出緊耦合的。

1.1.1 數據訪問層

要實現商品列表這一功能,首先要編寫數據訪問層,需要設計數據庫及表,在SQLServer中設計的數據庫表Product結構如下:

表設計好之后,就可以開始寫代碼了。在Visual Studio 中,新建一個名為DataAccessLayer的工程,添加一個ADO.NET Entity Data Model,此時Visual Studio的向導會自動幫我們生成Product實體和ObjectContext DB操作上下文。這樣我們的 Data Access Layer就寫好了。

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 分析

現在,按照三層“架構”我們的代碼寫好了,并且也達到了要求。整個項目的結構如下圖:

?

這應該是我們通常經常寫的所謂的三層架構。在Visual Studio中,三層之間的依賴可以通過項目引用表現出來。

1.2.1 依賴關系圖

現在我們來分析一下,這三層之間的依賴關系,很明顯,上面的實現中,DomianLogic需要依賴SqlDataAccess,因為DomainLogic中用到了Product這一實體,而這個實體是定義在DataAccess這一層的。WebUI這一層需要依賴DomainLogic,因為ProductService在這一層,同時,還需要依賴DataAccess,因為在UI中也用到了Product實體,現在整個系統的依賴關系是這樣的:

1.2.2 耦合性分析

使用三層結構的主要目的是分離關注點,當然還有一個原因是可測試性。我們應該將領域模型從數據訪問層和表現層中分離出來,這樣這兩個層的變化才不會污染領域模型。在大的系統中,這點很重要,這樣才能將系統中的不同部分隔離開來。

現在來看之前的實現中,有沒有模塊性,有沒有那個模塊可以隔離出來呢。現在添加幾個新的case來看,系統是否能夠響應這些需求:

添加新的用戶界面

除了WebForm用戶之外,可能還需要一個WinForm的界面,現在我們能否復用領域層和數據訪問層呢?從依賴圖中可以看到,沒有任何一個模塊會依賴表現層,因此很容易實現這一點變化。我們只需要創建一個WPF的富客戶端就可以。現在整個系統的依賴圖如下:

更換新的數據源

可能過了一段時間,需要把整個系統部署到云上,要使用其他的數據存儲技術,比如Azure Table Storage Service。現在,整個訪問數據的協議發生了變化,訪問Azure Table Storage Service的方式是Http協議,而之前的大多數.NET 訪問數據的方式都是基于ADO.NET 的方式。并且數據源的保存方式也發生了改變,之前是關系型數據庫,現在變成了key-value型數據庫。

由上面的依賴關系圖可以看出,所有的層都依賴了數據訪問層,如果修改數據訪問層,則領域邏輯層,和表現層都需要進行相應的修改。

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類,其中定義了一些簡單的用于數據展示的字段。

現在在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類中只定義了商品的基本信息比如名稱和單價。整個關系圖如下:

現在來看表現層,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 inthis.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.Productswhere p.IsFeaturedselect p).AsEnumerable();return from p in productsselect p.ToDomainProduct();} }

在這里需要注意的是,在領域模型層中,我們定義了一個名為Product的領域模型,然后再數據訪問層中Entity Framework幫我們也生成了一個名為Product的數據訪問層實體,他是和db中的Product表一一對應的。所以我們在方法返回的時候,需要把類型從db中的Product轉換為領域模型中的POCOs Product對象。

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 依賴關系圖

現在,整個系統的依賴關系圖如下:

表現層和數據訪問層都依賴領域模型層,這樣,在前面的case中,如果我們新添加一個UI界面;更換一種數據源的存儲和獲取方式,只需要修改對應層的代碼即可,領域模型層保持了穩定。

2.2.2 時序圖

整個系統的時序圖如下:

系統啟動的時候,在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)層。如下圖:

?

將Controllers和ViewModel從表現層移到了表現模型層,僅僅將視圖(.aspx和.ascx文件)和聚合根對象(Composition Root)保留在了表現層中。之所以這樣處理,是可以使得盡可能的使得表現層能夠可配置而其他部分盡可能的可以保持不變。

?

3. 結語


一不小心我們就編寫出了緊耦合的代碼,有時候以為分層了就可以解決這一問題,但是大多數的時候,都沒有正確的實現分層。之所以容易寫出緊耦合的代碼有一個原因是因為編程語言或者開發環境允許我們只要需要一個新的實例對象,就可以使用new關鍵字來實例化一個。如果我們需要添加依賴,Visual Studio有些時候可以自動幫我們添加引用。這使得我們很容易就犯錯,使用new關鍵字,就可能會引入以來;添加引用就會產生依賴。

減少new引入的依賴及緊耦合最好的方式是使用構造函數注入依賴這種設計模式:即如果我們需要一個依賴的實例,通過構造函數注入。在第二個部分的實現演示了如何針對抽象而不是具體編程。

構造函數注入是反轉控制的一個例子,因為我們反轉了對依賴的控制。不是使用new關鍵字創建一個實例,而是將這種行為委托給了第三方實現。

希望本文能夠給大家了解如何真正實現三層架構,編寫松散耦合,可維護,可測試性的代碼提供一些幫助。

作者:?yangecnu(yangecnu's Blog on 博客園)?
出處:http://www.cnblogs.com/yangecnu/?
本作品由yangecnu?創作,采用知識共享署名-非商業性使用-禁止演繹 2.5 中國大陸許可協議進行許可。 歡迎轉載,但任何轉載必須保留完整文章,在顯要地方顯示署名以及原文鏈接。如您有任何疑問或者授權方面的協商,請?給我留言。

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的浅谈依赖注入的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。