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