ABP(現代ASP.NET樣板開發框架)系列之6、ABP依賴注入
基於DDD的現代ASP.NET開發框架--ABP系列之6、ABP依賴注入
ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。
本文由 上海-半冷 提供翻譯
什麼是依賴注入
如果你已經知道依賴注入的概念,建構函式和屬性注入模式,你可以跳過這一節。
維基百科說:“依賴注入是一種軟體設計模式的一個或多個依賴項注入(或服務),或通過引用傳遞,為依賴物件(或客戶)和客戶端狀態的一部分。模式之間建立一個客戶的依賴關係的行為,它允許程式設計是鬆散耦合的,依賴倒置和單一職責原則。它直接對比service locator模式,它允許客戶瞭解他們所使用的系統找到依賴。”。
如果不使用依賴注入技術,很難進行依賴管理、模組化開發和應用程式模組化。
傳統方式的問題
在一個應用程式中,類之間相互依賴。假設我們有一個應用程式服務,使用倉儲(repository)類插入實體到資料庫。在這種情況下,應用程式服務類依賴於倉儲(repository)類。看下例子:
public class PersonAppService { private IPersonRepository _personRepository; public PersonAppService() { _personRepository= new PersonRepository(); } public void CreatePerson(string name, int age) { var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); } }
PersonAppService使用PersonRepository插入Person到資料庫。這段程式碼的問題:
- PersonAppService通過IPersonRepository呼叫CreatePerson方法,所以這方法依賴於IPersonRepository,代替了PersonRepository具體類。但PersonAppService(的建構函式)仍然依賴於PersonRepository。元件應該依賴於介面而不是實現。這就是所謂的依賴性倒置原則。
- 如果PersonAppService建立PersonRepository本身,它成為依賴IPersonRepository介面的具體實現,不能使用另一個實現。因此,此方式的將介面與實現分離變得毫無意義。硬依賴(hard-dependency)使得程式碼緊密耦合和較低的可重用。
- 我們可能需要在未來改變建立PersonRepository的方式。即,我們可能想讓它建立為單例(單一共享例項而不是為每個使用建立一個物件)。或者我們可能想要建立多個類實現IPersonRepository並根據條件建立物件。在這種情況下,我們需要修改所有依賴於IPersonRepository的類。
- 有了這樣的依賴,很難(或不可能)對PersonAppService進行單元測試。
為了克服這些問題,可以使用工廠模式。因此,建立的倉儲類是抽象的。看下面的程式碼:
public class PersonAppService { private IPersonRepository _personRepository; public PersonAppService() { _personRepository = PersonRepositoryFactory.Create(); } public void CreatePerson(string name, int age) { var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); } }
PersonRepositoryFactory是一個靜態類,建立並返回一個IPersonRepository。這就是所謂的服務定位器模式。以上依賴問題得到解決,因為PersonAppService不需要建立一個IPersonRepository的實現的物件,這個物件取決於PersonRepositoryFactory的Create方法。但是,仍然存在一些問題:
- 此時,PersonAppService取決於PersonRepositoryFactory。這是更容易接受,但仍有一個硬依賴(hard-dependency)。
- 為每個庫或每個依賴項乏味的寫一個工廠類/方法。
- 測試性依然不好,由於很難使得PersonAppService使用mock實現IPersonRepository。
解決方案
有一些最佳實踐(模式)用於類依賴。
建構函式注入
重寫上面的例子,如下所示:
public class PersonAppService { private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository) { _personRepository = personRepository; } public void CreatePerson(string name, int age) { var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); } }
這被稱為建構函式注入。現在,PersonAppService不知道哪些類實現IPersonRepository以及如何建立它。誰需要使用PersonAppService,首先建立一個IPersonRepository PersonAppService並將其傳遞給建構函式,如下所示:
var repository = new PersonRepository(); var personService = new PersonAppService(repository); personService.CreatePerson("Yunus Emre", 19);
建構函式注入是一個完美的方法,使一個類獨立建立依賴物件。但是,上面的程式碼有一些問題:
- 建立一個PersonAppService變得困難。想想如果它有4個依賴,我們必須建立這四個依賴物件,並將它們傳遞到建構函式PersonAppService。
- 從屬類可能有其他依賴項(在這裡,PersonRepository可能有依賴關係)。所以,我們必須建立PersonAppService的所有依賴項,所有依賴項的依賴關係等等. .如此,依賴關係使得我們建立一個物件變得過於複雜了。
幸運的是,依賴注入框架自動化管理依賴關係。
屬性注入
建構函式注入模式是一個完美的提供類的依賴關係的方式。通過這種方式,您不能建立類的例項,而不提供依賴項。它也是一個強大的方式顯式地宣告是什麼類的需求正確地工作。
但是,在某些情況下,該類依賴於另一個類,但也可以沒有它。這通常是適用於橫切關注點(如日誌記錄)。一個類可以沒有工作日誌,但它可以寫日誌如果你提供一個日誌物件。在這種情況下,您可以定義依賴為公共屬性,而不是讓他們放在建構函式。想想,如果我們想在PersonAppService寫日誌。我們可以重寫類如下:
public class PersonAppService { public ILogger Logger { get; set; } private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository) { _personRepository = personRepository; Logger = NullLogger.Instance; } public void CreatePerson(string name, int age) { Logger.Debug("Inserting a new person to database with name = " + name); var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); Logger.Debug("Successfully inserted!"); } }
NullLogger.Instance 是一個單例物件,實現了ILogger介面,但實際上什麼都沒做(不寫日誌。它實現了ILogger例項,且方法體為空)。現在,PersonAppService可以寫日誌了,如果你為PersonAppService例項設定了Logger,如下面:
var personService = new PersonAppService(new PersonRepository()); personService.Logger = new Log4NetLogger(); personService.CreatePerson("Yunus Emre", 19);
假設Log4NetLogger實現ILogger例項,使得我們可以使用Log4Net庫寫日誌。因此,PersonAppService可以寫日誌。如果我們不設定Logger,PersonAppService就不寫日誌。因此,我們可以說PersonAppService ILogger例項是一個可選的依賴。
幾乎所有的依賴注入框架都支援屬性注入模式
依賴注入框架
有許多依賴注入框架,都可以自動解決依賴關係。他們可以建立所有依賴項(遞迴地依賴和依賴關係)。所以你只需要根據注入模式寫類和類建構函式&屬性,其他的交給DI框架處理!在良好的應用程式中,類甚至獨立於DI框架。整個應用程式只會有幾行程式碼或類,顯示的與DI框架互動。
ABP的依賴注入基於 Castle Windsor框架。Castle Windsor最成熟的DI框架之一。還有很多這樣的框架,如Unity,Ninject,StructureMap,Autofac等等。
在使用一個依賴注入框架時,首先註冊您的介面/類到依賴注入框架中,然後你就可以resolve一個物件。在Castle Windsor,它是這樣的:
var container = new WindsorContainer(); container.Register( Component.For<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(), Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient() ); var personService = container.Resolve<IPersonAppService>(); personService.CreatePerson("Yunus Emre", 19);
我們首先建立了WindsorContainer。然後註冊PersonRepository 和 PersonAppService及它們的介面。然後我們要求容器建立一個IPersonAppService例項。它建立PersonAppService物件及其依賴項並返回。在這個簡單的示例中,使用DI框架也許不是那麼簡潔,但想象下,在實際的企業應用程式中你會有很多類和依賴關係。當然,註冊的依賴項只在程式啟動的某個地方建立一次。
請注意,我們只是講物件宣告為臨時物件(transient)。這意味著每當我們建立這些型別的一個物件時,就會建立一個新的例項。有許多不同的生命週期(如Singletion)。
ABP依賴注入的基礎結構
在編寫應用程式時遵循最佳實踐和一些約定,ABP幾乎讓依賴注入框架使用變得無形。
註冊
在ABP中,有很多種不同的方法來註冊你的類到依賴注入系統。大部分時間,常規方法就足夠了。
常規註冊
按照約定,ABP自動註冊所有 Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。例如,您可能有一個IPersonAppService 介面和實現類PersonAppService:
public interface IPersonAppService : IApplicationService { //... } public class PersonAppService : IPersonAppService { //... }
ABP會自動註冊它,因為它實現IApplicationService介面(它只是一個空的介面)。它會被註冊為transient (每次使用都建立例項)。當你注入(使用建構函式注入)IPersonAppService介面成一個類,PersonAppService物件會被自動建立並傳遞給建構函式。
命名約定在這裡非常重要。例如你可以將名字PersonAppService改為 MyPersonAppService或另一個包含“PersonAppService”字尾的名稱,由於IPersonAppService包含這個字尾。但是你可以不遵循PeopleService命名您的服務類。如果你這樣做,它將不會為IPersonAppService自動註冊(它需要自注冊(self-registration)到DI框架,而不是介面),所以,如果你想要你應該手動註冊它。
ABP按照約定註冊程式集。所以,你應該告訴ABP按照約定註冊您的程式集。這很容易:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Assembly.GetExecutingAssembly()得到一個對包括此程式碼的程式集的引用。你可以通過RegisterAssemblyByConvention方法註冊其他程式集。這同在你的模組初始化(AbpModule.Initialize())時完成。請檢視ABP的模組系統獲得更多資訊。
您可以通過實現IConventionalRegisterer介面和呼叫IocManager。AddConventionalRegisterer方法編寫自己的約定註冊類。你應該將它新增到模組的pre-initialize方法中。
幫助介面
你可以註冊一個特定的類,不遵循傳統的約定製度規則。ABP提供了ITransientDependency和ISingletonDependency介面的快捷方法。例如:
public interface IPersonManager { //... } public class MyPersonManager : IPersonManager, ISingletonDependency { //... }
以這種方式,您可以很容易地註冊MyPersonManager為transient。當需要注入IPersonManager時,MyPersonManager會被使用。注意,依賴被宣告為單例。因此,建立的MyPersonManager同一個物件被傳遞給所有需要的類。只是在第一次使用時建立,那麼應用程式的整生命週期使用的是同一例項。
自定義/直接 註冊
如果之前描述的方法還是不足以應對你的情況,你可以使用Castle Windsor註冊類和及依賴項。因此,您將擁有Castle Windsor註冊的所有能力。
可以實現IWindsorInstaller介面進行註冊。您可以在應用程式中建立一個實現IWindsorInstaller介面的類:
public class MyInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf()); } }
Abp自動發現和執行這個類。最後,你可以通過使用IIocManager.IocContainer屬性得到WindsorContainer。有關更多資訊,閱讀Windsor的文件。
解析(Resolving)
註冊通知IOC(控制反轉)容器關於你的類,它們的依賴項和生命週期。在您的應用程式需要使用IOC容器建立物件時,ASP.NET提供了一些方法解決依賴關係。
建構函式 & 屬性注入
作為最佳實踐,你可以使用建構函式和屬性注入去獲取你的類的依賴。任何可能的地方,你都應該這樣做。例子:
public class PersonAppService { public ILogger Logger { get; set; } private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository) { _personRepository = personRepository; Logger = NullLogger.Instance; } public void CreatePerson(string name, int age) { Logger.Debug("Inserting a new person to database with name = " + name); var person = new Person { Name = name, Age = age }; _personRepository.Insert(person); Logger.Debug("Successfully inserted!"); } }
IPersonRepository從建構函式注入,ILogger例項從公共屬性注入。這樣,您的程式碼不會體現依賴注入系統。這是使用DI系統最適當的方式。
IIocResolver 和 IIocManager
有時你可能需要直接建立你的依賴項,而不是建構函式和屬性注入。應該儘可能避免這種情況,但它可能無法避免。Abp提供一些服務使得這樣的注入很容易實現。例子:
public class MySampleClass : ITransientDependency { private readonly IIocResolver _iocResolver; public MySampleClass(IIocResolver iocResolver) { _iocResolver = iocResolver; } public void DoIt() { //Resolving, using and releasing manually var personService1 = _iocResolver.Resolve<PersonAppService>(); personService1.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" }); _iocResolver.Release(personService1); //Resolving and using in a safe way using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>()) { personService2.Object.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" }); } } }
MySampleClass是一個應用程式的示例類。IIcResolver通過建構函式注入,然後用它來建立和釋放物件。有幾個解決方法的過載可以根據需要使用。Release方法用於釋放元件(物件)。如果你是手動建立一個物件,呼叫Release方法釋放物件非常重要。否則,您的應用程式會有記憶體洩漏問題。為了保證物件被釋放,儘可能使用ResolveAsDisposable(就像上面的例子所示)。它會在using程式碼塊結束的時候自動呼叫Release方法。
如果你想直接使用IOC容器(Castle Windsor)來處理依賴關係項,可以通過建構函式注入 IIocManager並使用它IIocManager.IocContainer 屬性。如果你是在一個靜態上下文或不能注入IIocManager,還有最後一個方法,你可以使用單例物件IocManager.Instance,你可以在任何地方獲取到,它無處不在。但是,在這種情況下你的程式碼將變得不易容測試。
附加
IShouldInitialize 介面
有些類在第一次使用前需要初始化。IShouldInitialize有Initialize()方法。如果你實現它,那麼你的Initialize()方法自動會被自動呼叫在建立物件之後(在使用之前)。當然,為了使用這個特性,你應該注入/建立此物件。
ASP.NET MVC & ASP.NET Web API 整合
當然,我們必須呼叫依賴注入系統處理依賴關係圖的根物件。在一個ASP.NET MVC應用程式,通常是一個控制器類。我們可以使用建構函式注入模式注入控制器。當一個請求來到我們的應用程式中,控制器和所有依賴項被IOC容器遞迴建立。所以,誰做了這些?這是被Abp擴充套件的ASP.NET MVC預設控制器工廠自動完成的。ASP.NET Web API 也是相似的。你不用關心物件的建立和釋放。
最後說明
Abp簡化並自動使用依賴注入,只要你遵守規則和使用上面的結構。大多數時候這樣就夠了。但是如果不能滿足你的需求,你可以直接使用Castle Windsor的所有能力來執行任何任務(如自定義註冊,注入鉤子,攔截器等等)。
希望更多國內的架構師能關注到ABP這個專案,也許這其中有能幫助到您的地方,也許有您的參與,這個專案可以發展得更好。
歡迎加QQ群:
相關推薦
ABP(現代ASP.NET樣板開發框架)系列之6、ABP依賴注入
基於DDD的現代ASP.NET開發框架--ABP系列之6、ABP依賴注入 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由 上海-半冷 提供翻譯 什麼是依賴注入 如果你已經知道依賴注入的概念,建構函式和屬性注入
ABP(現代ASP.NET樣板開發框架)系列之10、ABP領域層——實體
基於DDD的現代ASP.NET開發框架--ABP系列之10、ABP領域層——實體 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由深圳-Carl提供翻譯 實體是DDD(領域驅動設計)的核心概念之一。Eric Eva
ABP(現代ASP.NET樣板開發框架)系列之4、ABP模組系統
基於DDD的現代ASP.NET開發框架--ABP系列之4、ABP模組系統 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 ABP模組系統簡介 ABP框架提供了建立和組裝模組的基礎,一個模組
ABP(現代ASP.NET樣板開發框架)系列之9、ABP設定管理
基於DDD的現代ASP.NET開發框架--ABP系列之9、ABP設定管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由山東-李偉提供翻譯 介紹 每個應用程式需要儲存一些設定並在應用程式的某個地方使用這些設定。
ABP(現代ASP.NET樣板開發框架)系列之2、ABP入門教程
基於DDD的現代ASP.NET開發框架--ABP系列之2、ABP入門教程 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的
ABP(現代ASP.NET樣板開發框架)系列之17、ABP應用層——引數有效性驗證
基於DDD的現代ASP.NET開發框架--ABP系列之17、ABP應用層——引數有效性驗證 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 應用程式的輸入資料首先應該被檢驗是否有效。輸入的資料能被使用者或其他應用程式提
ABP(現代ASP.NET樣板開發框架)系列之16、ABP應用層——資料傳輸物件(DTOs)
基於DDD的現代ASP.NET開發框架--ABP系列之16、ABP應用層——資料傳輸物件(DTOs) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 資料傳輸物件(Data Transfer Objects)用於應用層
ABP(現代ASP.NET樣板開發框架)系列之13、ABP領域層——資料過濾器(Data filters)
基於DDD的現代ASP.NET開發框架--ABP系列之13、ABP領域層——資料過濾器(Data filters) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 介紹 在資料庫開發中,我們一般會運用軟刪除(soft
ABP(現代ASP.NET樣板開發框架)系列之21、ABP展現層——Javascript函式庫
基於DDD的現代ASP.NET開發框架--ABP系列之21、ABP展現層——Javascript函式庫 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate的js庫提供了一些讓java
ABP(現代ASP.NET樣板開發框架)系列之14、ABP領域層——領域事件(Domain events)
基於DDD的現代ASP.NET開發框架--ABP系列之14、ABP領域層——領域事件(Domain events) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在C#中,一個類可以定義其專屬的事件並且其它類可以註冊該事
ABP(現代ASP.NET樣板開發框架)系列之5、ABP啟動配置
基於DDD的現代ASP.NET開發框架--ABP系列之5、ABP啟動配置 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由 東莞-天道 提供翻譯 譯者注:在看這一節的內容之前,建議大家先下載module-ze
ABP(現代ASP.NET樣板開發框架)系列之3、ABP分層架構
基於DDD的現代ASP.NET開發框架--ABP系列之3、ABP分層架構 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 前言 為了減少複雜性和提高程式碼的可重用性,採用分層架構是一種被廣泛接受的技術。為了實現分層的
ABP(現代ASP.NET樣板開發框架)系列之19、ABP應用層——審計日誌
基於DDD的現代ASP.NET開發框架--ABP系列之19、ABP應用層——審計日誌 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 維基百科定義:審計跟蹤(也稱為稽核日誌)是一個安全相關的時間順序記錄,記錄這些記錄的
ABP(現代ASP.NET樣板開發框架)系列之15、ABP應用層——應用服務(Application services)
基於DDD的現代ASP.NET開發框架--ABP系列之15、ABP應用層——應用服務(Application services) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 應用服務用於將領
ABP(現代ASP.NET樣板開發框架)系列之23、ABP展現層——異常處理
基於DDD的現代ASP.NET開發框架--ABP系列之23、ABP展現層——異常處理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在 web 應用程式中,異常通常是在 MVC Controller actions
ABP(現代ASP.NET樣板開發框架)系列之8、ABP日誌管理
基於DDD的現代ASP.NET開發框架--ABP系列之8、ABP日誌管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 Server side(伺服器端) ASP.NET Boilerpla
ABP(現代ASP.NET樣板開發框架)系列之1、ABP總體介紹
基於DDD的現代ASP.NET開發框架--ABP系列之1、ABP總體介紹 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的
ABP(現代ASP.NET樣板開發框架)系列之12、ABP領域層——工作單元(Unit Of work)
基於DDD的現代ASP.NET開發框架--ABP系列之12、ABP領域層——工作單元(Unit Of work) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 通用連線和事務管理方法 連線和事務管理是使用資料庫的應用程
ABP(現代ASP.NET樣板開發框架)系列之22、ABP展現層——導航欄設定
基於DDD的現代ASP.NET開發框架--ABP系列之22、ABP展現層——導航欄設定 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 每一個WEB應用程式都有導航選單,Abp也為使用者提供了通用的建立和顯示選單方式。
ABP(現代ASP.NET樣板開發框架)系列之7、ABP Session管理
基於DDD的現代ASP.NET開發框架--ABP系列之7、ABP Session管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 簡介 如果一個應用程式需要登入,則它必須知道當前使用者執行了什麼操作。因此ASP.