ABP理论学习之依赖注入
返回總目錄
本篇目錄
- 什么是依賴注入
- 傳統(tǒng)方式產(chǎn)生的問(wèn)題
- 解決辦法
- 依賴注入框架
- ABP中的依賴注入基礎(chǔ)設(shè)施
- 注冊(cè)
- 解析
- 其他
- ASP.NET MVC和ASP.NET Web API集成
- 最后提示
什么是依賴注入
維基百科說(shuō):“依賴注入是一種軟件設(shè)計(jì)模式,在這種模式下,一個(gè)或更多的依賴(或服務(wù))被注入(或者通過(guò)引用傳遞)到一個(gè)獨(dú)立的對(duì)象(或客戶端)中,然后成為了該客戶端狀態(tài)的一部分。該模式分離了客戶端依賴本身行為的創(chuàng)建,這使得程序設(shè)計(jì)變得松耦合,并遵循了依賴反轉(zhuǎn)和單一職責(zé)原則。與服務(wù)定位器模式形成直接對(duì)比的是,它允許客戶端了解客戶端如何使用該系統(tǒng)找到依賴”。
不使用依賴注入技巧來(lái)管理依賴,并開(kāi)發(fā)一個(gè)模塊化的,結(jié)構(gòu)友好的應(yīng)用是非常困難的。
傳統(tǒng)方式產(chǎn)生的問(wèn)題
在一個(gè)應(yīng)用中,類相互依賴。假設(shè)我們有個(gè)應(yīng)用服務(wù),該應(yīng)用服務(wù)使用了倉(cāng)儲(chǔ)將實(shí)體插入數(shù)據(jù)庫(kù)。在這種情況下,此應(yīng)用服務(wù)類依賴于倉(cāng)儲(chǔ)類。看下面這個(gè)例子:
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將一個(gè) Person插入到數(shù)據(jù)庫(kù)中。此處代碼的問(wèn)題在于:
- PersonAppService在CreatePerson方法中使用了IPersonRepository的引用,因此該方法依賴于IPersonRepository,而不是具體的PersonRepository類。但是在PersonAppService的構(gòu)造函數(shù)中仍舊依賴于PersonRepository。而組件應(yīng)該依賴于接口而不是實(shí)現(xiàn),這就是依賴反轉(zhuǎn)原則。
- 如果PersonAppService創(chuàng)建了PersonRepository本身,那么它會(huì)依賴于IPersonRepository接口的一個(gè)具體實(shí)現(xiàn),這樣就造成可能不會(huì)和其他實(shí)現(xiàn)一起工作。因此,從實(shí)現(xiàn)中分離接口就會(huì)變得毫無(wú)意義。硬依賴使得代碼基變得緊耦合,可復(fù)用性降低。
- 在未來(lái)我們可能需要改變PersonRepository的創(chuàng)建。比如,我們可能想要它是單例的(單一公用的實(shí)例而不是每次使用都創(chuàng)建一個(gè)對(duì)象)。或者我們可能不止會(huì)創(chuàng)建實(shí)現(xiàn)了IPersonRepository的一個(gè)類,也可能想要有條件地創(chuàng)建這些實(shí)現(xiàn)類中的一個(gè)。這種情況下,我們就要改變依賴IPersonRepository的所有類,這樣太不方便了,或者說(shuō)維護(hù)難度太大了。
- 測(cè)試方面,有了這么個(gè)依賴,對(duì)于PersonAppService的單元測(cè)試非常難(或者根本不可能)。
為了克服這些問(wèn)題,可以使用工廠模式。因此,倉(cāng)儲(chǔ)類的創(chuàng)建時(shí)抽象的。看下面的代碼:
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是一個(gè)創(chuàng)建并返回一個(gè)IPersonRepository的靜態(tài)類。這就是所謂的服務(wù)定位器模式。這樣創(chuàng)建問(wèn)題是解決了,因?yàn)镻ersonAppService不知道如何創(chuàng)建一個(gè)IPersonRepository的實(shí)現(xiàn),而且它獨(dú)立于PersonRepository的實(shí)現(xiàn)。但是,仍然有下面這些問(wèn)題:
- 這次,PersonAppService依賴于PersonRepositoryFactory。這個(gè)較為可接受,但是仍然有硬依賴。
- 為每個(gè)倉(cāng)儲(chǔ)或者依賴寫(xiě)一個(gè)工廠類或方法太繁瑣了。
- 還是不太好測(cè)試,因?yàn)樽孭ersonAppService使用一些IPersonRepository的偽造實(shí)現(xiàn)還是很困難。
解決辦法
要依賴其他的類有一些最佳實(shí)踐(模式)。
構(gòu)造函數(shù)注入模式
上面的例子可以重寫(xiě)為下面的代碼:
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);} }這就是所謂的構(gòu)造函數(shù)注入。現(xiàn)在,PersonAppService不知道哪一個(gè)類實(shí)現(xiàn)了IPersonRepository,也不知道如何創(chuàng)建的它。誰(shuí)要使用PersonAppService,首先要?jiǎng)?chuàng)建一個(gè)IPersonRepository,并將它傳給PersonAppService的構(gòu)造函數(shù),如下所示:
var repository = new PersonRepository(); var personService = new PersonAppService(repository); personService.CreatePerson("Yunus Emre", 19);構(gòu)造函數(shù)注入是使類獨(dú)立于依賴對(duì)象創(chuàng)建的一種完美方式,但是,上面的代碼存在一些問(wèn)題:
- 創(chuàng)建一個(gè)PersonAppService變得更加困難。試想如果它有4個(gè)依賴,那么我們必須創(chuàng)建這4個(gè)依賴的對(duì)象,然后把它們傳入PersonAppService的構(gòu)造函數(shù)中。
- 依賴的類可能有其它的依賴(這里,PersonRepository可能有依賴)。因此,我們必須創(chuàng)建PersonAppService的所有依賴,依賴的所有依賴等等。這樣的話,我們甚至可能不再創(chuàng)建單一對(duì)象,因?yàn)橐蕾噲D太復(fù)雜了。
幸運(yùn)的是,ABP有依賴注入框架自動(dòng)管理依賴。
屬性注入模式
構(gòu)造函數(shù)注入是提供一個(gè)類的依賴的完美模式。用這種方式,你可以不需要提供依賴就能創(chuàng)建一個(gè)類的實(shí)例,它也是顯示聲明該類需要滿足什么要求才能正確工作的強(qiáng)大方式。
但在某些情況下,該類依賴于其他的類而且其他的類沒(méi)有它也能工作。這對(duì)于關(guān)注度分離(比如日志記錄)來(lái)說(shuō)經(jīng)常是成立的。一個(gè)類可以離開(kāi)logging工作,但如果提供了logger,那它就能記錄日志。這種情況下,你可以定義將依賴定義為公共的屬性而不是在構(gòu)造函數(shù)中獲得這些依賴。試想如果我們要在PersonAppService中記錄日志,那么我們可以重寫(xiě)該類為:
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是一個(gè)實(shí)現(xiàn)了ILogger的單例對(duì)象,但實(shí)際上什么都沒(méi)做(沒(méi)有記錄日志,它使用了空的方法體實(shí)現(xiàn)了ILogger)。因此,如果你在創(chuàng)建PersonAppService對(duì)象之后,并像下面那樣設(shè)置了Logger,PersonAppService就可以記錄日志了:
var personService = new PersonAppService(new PersonRepository()); personService.Logger = new Log4NetLogger(); personService.CreatePerson("Yunus Emre", 19);假設(shè)Log4NetLogger實(shí)現(xiàn)了ILogger并使用Log4Net類庫(kù)記錄日志。這樣,PersonAppService實(shí)際上就可以記錄日志了。如果沒(méi)有設(shè)置Logger,那么它就不會(huì)記錄日志。因此,我們可以說(shuō)ILogger是PersonAppService的一個(gè)可選依賴。
幾乎所有的依賴注入框架都支持屬性注入模式。
依賴注入框架
有很多自動(dòng)解析依賴的依賴注入框架。它們能夠使用所有的依賴(包括依賴的依賴)創(chuàng)建對(duì)象。因此,你只需要使用構(gòu)造和屬性注入模式編寫(xiě)你的類,DI框架會(huì)處理剩下的事情。在一個(gè)優(yōu)秀的應(yīng)用中,你的類甚至獨(dú)立于DI框架。在整個(gè)應(yīng)用中,有許多顯式和DI框架交互的代碼行或者類。
ABP使用Castle Windsor框架處理依賴注入。它是最成熟的DI框架之一。還有很多其他的框架,如Unity,Ninject,StructureMap,Autofac等等。
在依賴注入框架中,你首先要將你的接口或者類注冊(cè)到其中,然后才可以解析(創(chuàng)建)一個(gè)對(duì)象。在Castle Windsor中,有點(diǎn)像下面那樣:
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);上面的代碼中,首先創(chuàng)建了WindsorContainer,然后使用PersonRepository和PersonAppService的接口注冊(cè)了它們,再然后我們要求容器創(chuàng)建一個(gè)IPersonAppService。容器使用依賴創(chuàng)建了PersonAppService并返回,也許在這個(gè)簡(jiǎn)單的例子中使用DI框架的優(yōu)勢(shì)不是很明顯,但是想象一下你在一個(gè)真實(shí)的企業(yè)應(yīng)用中會(huì)有很多類和依賴。當(dāng)然,也會(huì)在別的地方使用對(duì)象來(lái)注冊(cè)依賴,這個(gè)在應(yīng)用啟動(dòng)時(shí)只會(huì)做一次。
注意,我們也將對(duì)象的生命周期聲明為transient。這意味著,無(wú)論何時(shí)解析這些類型的一個(gè)對(duì)象,都會(huì)創(chuàng)建一個(gè)新的實(shí)例。當(dāng)然還有很多不同的生命周期(像singleton)。
ABP中的依賴注入基礎(chǔ)設(shè)施
當(dāng)你通過(guò)下面的最佳實(shí)踐和一些慣例編寫(xiě)你的應(yīng)用時(shí),ABP幾乎讓使用DI框架變得不可見(jiàn)了。
注冊(cè)
在ABP中,將你的類注冊(cè)到DI系統(tǒng)有幾種不同的方式。大多數(shù)情況下,按照慣例注冊(cè)已經(jīng)足夠了。
慣例注冊(cè)
ABP會(huì)按照慣例自動(dòng)注冊(cè)所有的倉(cāng)儲(chǔ),領(lǐng)域服務(wù),應(yīng)用服務(wù),MVC控制器和Web API控制器。比如,你可能有一個(gè)IPersonAppService接口和一個(gè)實(shí)現(xiàn)了該接口的PersonAppService類:
public interface IPersonAppService : IApplicationService {//... }public class PersonAppService : IPersonAppService {//... }因?yàn)樗鼘?shí)現(xiàn)了IApplicationService接口(只是一個(gè)空接口),所以ABP會(huì)自動(dòng)注冊(cè)它,并注冊(cè)為transient(每次使用創(chuàng)建一個(gè)實(shí)例)。當(dāng)你使用構(gòu)造函數(shù)注入IPersonAppService接口到一個(gè)類中時(shí),一個(gè)PersonAppService對(duì)象會(huì)自動(dòng)地創(chuàng)建并傳入該類的構(gòu)造函數(shù)中。
命名規(guī)范在ABP中非常重要。比如,你可以將PersonAppService更名為MyPersonAppService或是其他包含了“PersonAppService”后綴的名字,因?yàn)镮PersonAppService接口有這個(gè)后綴。但你不能將它命名為PeopleService。如果你沒(méi)有按照這種命名規(guī)范來(lái)操作的話,那么IPersonAppService不會(huì)自動(dòng)地注冊(cè)(但是它已經(jīng)以自注冊(cè)的方式注入到DI框架,而不是接口方式),因此如果你想要以接口方式注冊(cè)的話,那么你應(yīng)該手動(dòng)注冊(cè)。
ABP按照慣例注冊(cè)程序集。因此,你應(yīng)該按照慣例告訴ABP注冊(cè)你的程序集。這個(gè)相當(dāng)簡(jiǎn)單:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());Assembly.GetExecutingAssembly()會(huì)獲得包含這句代碼的程序集的引用。你也可以將其他的程序集傳入RegisterAssemblyByConvention 方法中。這個(gè)操作通常在你的模塊初始化的時(shí)候完成的。查看《模塊系統(tǒng)》博文獲得更多信息。
通過(guò)實(shí)現(xiàn)IConventionalRegister接口和調(diào)用IocManager.AddConventionalRegister方法,你可以用你的類編寫(xiě)你自己的慣例注冊(cè)類。你要做的就是在模塊的PreInitialize方法中加入它。
幫助接口
你可能想要注冊(cè)一個(gè)特殊的類,但是它不符合慣例注冊(cè)的原則。為此,ABP提供了ITransientDependency 和 ISingletonDependency接口。比如:
public interface IPersonManager {//... }public class MyPersonManager : IPersonManager, ISingletonDependency {//... }用這種方式,你可以輕松地注冊(cè)MyPersonManager。當(dāng)需要注入IPersonManager的時(shí)候,就會(huì)使用MyPersonManager。注意依賴聲明為Singleton。這樣,MyPersonManager的單例就被創(chuàng)建了,并且相同的對(duì)象也被傳入到所有的類中。只有在第一次使用時(shí)才會(huì)創(chuàng)建,以后再整個(gè)應(yīng)用的生命周期都會(huì)使用相同的實(shí)例。
自定義/直接注冊(cè)
如果之前描述的方法還不能滿足你,那么你可以直接使用Castle Windsor來(lái)注冊(cè)你的類和依賴。這樣,你就在Castle Windsor中注冊(cè)任何東西。
Castle Windsor有一個(gè)為了注冊(cè)而要實(shí)現(xiàn)的接口IWindsorInstaller。你可以在應(yīng)用中創(chuàng)建實(shí)現(xiàn)了IWindsorInstaller接口的類:
public class MyInstaller : IWindsorInstaller {public void Install(IWindsorContainer container, IConfigurationStore store){container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());} }ABP會(huì)自動(dòng)找到并執(zhí)行這個(gè)類。最后,可以使用IIocManager.IocContainer屬性到達(dá)WindsorContainer。獲取更多Windsor信息,請(qǐng)查看官方文檔。
解析
注冊(cè)會(huì)將你的類,類的依賴和生命周期通知給IOC(控制反轉(zhuǎn))容器。接下來(lái),你需要在應(yīng)用中的某些地方使用IOC容器創(chuàng)建對(duì)象。ABP針對(duì)依賴的解析提供了很多選項(xiàng)。
構(gòu)造函數(shù)&屬性注入
你可以將使用構(gòu)造函數(shù)和屬性注入獲得類的依賴作為最佳實(shí)踐。無(wú)論在哪里,你都應(yīng)該這樣做。例如:
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從構(gòu)造函數(shù)注入,ILogger使用公共屬性注入。這樣的話,你的代碼根本意識(shí)不到依賴注入系統(tǒng)的存在,也就是說(shuō),依賴系統(tǒng)對(duì)于我們開(kāi)發(fā)者完全是透明的,我們可以不考慮依賴系統(tǒng)內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。這是使用DI系統(tǒng)最合適的方式。
IIocResolver和IIocManager
有時(shí),你可能必須要直接解析依賴而不是通過(guò)構(gòu)造函數(shù)和屬性注入。這種情況要盡可能地避免,但這種情況也是有可能的。ABP提供了很多可以輕松注入并使用的服務(wù)。例如:
public class MySampleClass : ITransientDependency {private readonly IIocResolver _iocResolver;public MySampleClass(IIocResolver iocResolver){_iocResolver = iocResolver;}public void DoIt(){//手動(dòng)解析var personService1 = _iocResolver.Resolve<PersonAppService>();personService1.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });_iocResolver.Release(personService1);//安全地解析并使用using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>()){personService2.Object.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });}} }在以上例子中的MySampleClass通過(guò)構(gòu)造函數(shù)注入IIocResolver并用它來(lái)解析和釋放對(duì)象。Resolve方法有許多重載可供使用。Release方法用來(lái)釋放組件(對(duì)象)。調(diào)用Release來(lái)手動(dòng)解析一個(gè)對(duì)象是很關(guān)鍵的,否則,應(yīng)用會(huì)有內(nèi)存泄漏問(wèn)題。為了確保釋放對(duì)象,要盡可能使用ResolveAsDisPosable(如例子中演示的那樣)。在using塊的末尾會(huì)自動(dòng)地調(diào)用Release。
如果你想要直接使用IOC容器(Castle Windor)來(lái)解析依賴,那么你可以構(gòu)造函數(shù)注入IIocManager并使用IIocManager.IocContainer屬性。如果你處于靜態(tài)上下文或者不能注入IIocManager,那么最后的機(jī)會(huì)就是,你可以使用單例對(duì)象IocManager.Instance。但是,這種情況不容易測(cè)試。
其他
IShouldInitialize接口
某些類在第一次使用前就要初始化。IShouldInitialize接口有一個(gè)Initialize方法。如果實(shí)現(xiàn)了該接口,那么在創(chuàng)建對(duì)象之后(使用前)就會(huì)自動(dòng)地調(diào)用Initialize方法。當(dāng)然,為了使該功能有效,你應(yīng)該注入/解析該對(duì)象。
ASP.NET MVC和ASP.NET Web API集成
當(dāng)然,為了解析依賴圖中的根對(duì)象,我們必須調(diào)用依賴注入系統(tǒng)。在ASP.NET MVC應(yīng)用中,根對(duì)象一般是一個(gè)Controller類。我們也可以在控制器中使用構(gòu)造函數(shù)注入和屬性注入模式。當(dāng)一個(gè)請(qǐng)求到達(dá)應(yīng)用時(shí),IOC容器創(chuàng)建了控制器對(duì)象,然后所有的依賴遞歸地解析出來(lái)。那么,誰(shuí)處理的這個(gè)呢?這是ABP通過(guò)擴(kuò)展了ASP.NET MVC默認(rèn)的控制器工廠自動(dòng)完成的。相似地,對(duì)于ASP.Net Web API也是如此。你不必關(guān)心創(chuàng)建和釋放對(duì)象的事情。
最后提示
只要你遵循規(guī)則并使用上面的結(jié)構(gòu),ABP就能簡(jiǎn)化并自動(dòng)化依賴注入的使用。大多數(shù)情況下,這些已經(jīng)夠用了。但是,如果你需要的話,你可以直接使用所有Castle Windsor的能力來(lái)執(zhí)行任何任務(wù)(如自定義注冊(cè),注入鉤子,攔截器等等)。
轉(zhuǎn)載于:https://www.cnblogs.com/farb/p/ABPDependencyInjection.html
總結(jié)
以上是生活随笔為你收集整理的ABP理论学习之依赖注入的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: RCE、exp、Exploit、Expl
- 下一篇: [c++]Struct和Class的区别