日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

DIP原则、IoC以及DI

發(fā)布時(shí)間:2023/12/4 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DIP原则、IoC以及DI 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、DIP原則

  • 高層模塊不應(yīng)該依賴于底層模塊,二者都應(yīng)該依賴于抽象。

  • 抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。

該原則理解起來(lái)稍微有點(diǎn)抽象,我們可以將該原則通俗的理解為:"依賴于抽象”

該規(guī)則告訴我們,程序中所有的依賴關(guān)系都應(yīng)該終止于抽象類或者接口,從而達(dá)到松耦合的目的。因?yàn)槲覀冊(cè)趹?yīng)用程序中編寫(xiě)的大多數(shù)具體類都是不穩(wěn)定的。我們不想直接依賴于這些不穩(wěn)定的具體類。通過(guò)把它們隱藏在抽象和接口的后面,可以隔離它們的不穩(wěn)定性。

舉個(gè)例子

一個(gè)Button對(duì)象會(huì)觸發(fā)Click方法,當(dāng)被按下時(shí),會(huì)調(diào)用Light對(duì)象的TurnOn方法,否則會(huì)調(diào)用Light對(duì)象的TurnOff方法。

這個(gè)設(shè)計(jì)存在兩個(gè)問(wèn)題:

  • Button類直接依賴于Light類,這種依賴關(guān)系意味著當(dāng)Light改變時(shí),Button類會(huì)受到影響;

  • Button對(duì)象只能控制Light對(duì)象,想要控制電視或者冰箱就不行了;

  • 新的設(shè)計(jì):

    這個(gè)方案對(duì)那些需要被Button控制的對(duì)象提出了一個(gè)約束。需要被Button控制的對(duì)象必須要實(shí)現(xiàn)ISwitchableDevice接口。

    所為原則,只是描述了什么是對(duì)的,但是并沒(méi)有說(shuō)清楚如何去做。在軟件工程中,我們經(jīng)常使用DI(依賴注入)來(lái)達(dá)到這個(gè)目的。但是提到依賴注入,人們又會(huì)經(jīng)常提起IoC這個(gè)術(shù)語(yǔ)。所以先讓我們來(lái)了解下什么是IoC。

    二、IoC

    IoC的全名是Inverse of Control,即控制反轉(zhuǎn)。這一術(shù)語(yǔ)并不是用來(lái)描述面向?qū)ο蟮哪撤N原則或者模式,IoC體現(xiàn)為一種流程控制的反轉(zhuǎn),一般用來(lái)對(duì)框架進(jìn)行設(shè)計(jì)。

    舉個(gè)例子

    ReportService是一個(gè)用來(lái)顯示報(bào)表的流程,該流程包括Trim(),Clean(),Show()三個(gè)環(huán)節(jié)。

    public class ReportService{
    ? ?private string _data; ?

    ?public ReportService(string data) ? ?{_data = data;} ? ?public void Trim(string data) ? ?{_data = data.Trim();} ? ?public void Clean() ? ?{_data = _data.Replace("@", "");_data = _data.Replace("-", ""); ? ? ? ?//...other rules} ? ?public void Show() ? ?{Console.WriteLine(_data);}}

    客戶端通過(guò)下面的方式使用該服務(wù):

    var reportService = new ReportService(input); reportService.Trim(input); reportService.Clean(); reportService.Show();

    這樣的一個(gè)設(shè)計(jì)體現(xiàn)了過(guò)程式的思考方式,客戶端依次調(diào)用每個(gè)環(huán)節(jié)從而組成了整個(gè)報(bào)表顯示流程,這樣的代碼體現(xiàn)了:客戶端擁有流程控制權(quán)

    我們來(lái)分析下這段代碼,ReportService提供了3個(gè)可重用的Api,正如ReportService的命名一樣,它告訴我們它是一個(gè)服務(wù),我們只能重用他提供的三個(gè)服務(wù),它無(wú)法提供一個(gè)打印報(bào)表的流程,整個(gè)流程是客戶端來(lái)控制的。

    另外,該設(shè)計(jì)也違反了tell, Don't ask原則。

    打印報(bào)表作為一個(gè)可復(fù)用的流程,不但可以提供可復(fù)用的流程環(huán)節(jié),還可以提供可復(fù)用的流程的定義,當(dāng)我們進(jìn)行框架設(shè)計(jì)的時(shí)候,往往會(huì)將整個(gè)流程控制定制在框架之中,然后提供擴(kuò)展點(diǎn)供客戶端定制。這樣的思想體現(xiàn)了流程的所有權(quán)從客戶端到框架的反轉(zhuǎn)。

    比如asp.net mvc或者asp.net api框架,內(nèi)部定義了http消息從請(qǐng)求,model binder,controller的激活,action的執(zhí)行,返回response
    等可復(fù)用的流程。同時(shí)還提供了每一個(gè)環(huán)節(jié)的可擴(kuò)展點(diǎn)。

    利用以上思想,我們對(duì)ReportService重新設(shè)計(jì)。

    新的設(shè)計(jì)

    采用IoC思想重新設(shè)計(jì)該報(bào)表服務(wù),將原來(lái)客戶端擁有的流程控制權(quán)反轉(zhuǎn)在報(bào)表服務(wù)框架中。ReportService這樣的命名已經(jīng)不適合我們的想法,新的實(shí)現(xiàn)不但提供了報(bào)表打印的相關(guān)服務(wù),同時(shí)還提供了一個(gè)可復(fù)用的流程,因此重新命名為ReportEngine。我們可以通過(guò)模板方法達(dá)到此目的:

    public class ReportEngine{ ? ?private ?string _data; ? ?public ReportEngine(string data) ? ?{_data = data;} ? ?public void Show() ? ?{Trim();Clean();Display();} ? ?public virtual void Trim() ? ?{_data = _data.Trim();} ? ?public virtual void Clean() ? ?{_data = _data.Replace("@", "");_data = _data.Replace("-", "");} ? ?public virtual void Display() ? ?{Console.WriteLine(_data);}}

    此時(shí)的報(bào)表服務(wù)在Show()方法中定義好了一組可復(fù)用的流程,客戶端只需要根據(jù)自己的需求重寫(xiě)每個(gè)環(huán)節(jié)即可。客戶端可以通過(guò)下面的方式使用ReportEngine

    var reportEngine=new StringReportEngine(input); reportEngine.Show();

    三、DI(Dependency Injection)

    DI即依賴注入,主要解決了2個(gè)問(wèn)題:

  • 松耦合,由DI容器來(lái)創(chuàng)建對(duì)象,符合DIP原則;

  • 符合IoC的思想,整個(gè)應(yīng)用程序事先定義好了一套可工作的流程,通過(guò)在客戶端替換DI容器中的具體實(shí)現(xiàn)達(dá)到重寫(xiě)某個(gè)組件的目的;

  • 除此之外,使用依賴注入還可以帶來(lái)以下好處:

    • 促使你寫(xiě)出更加符合面向?qū)ο笤瓌t的代碼,符合優(yōu)先使用對(duì)象組合,而不是繼承的原則;

    • 使系統(tǒng)更加具有可測(cè)試性;

    • 使系統(tǒng)更加具備可擴(kuò)展性和可維護(hù)性;

    • 由于所有組件都由DI容器管理,所以可以很方便的實(shí)現(xiàn)AOP攔截

    我記得之前在stackoverflow上看到過(guò)類似這樣的一個(gè)問(wèn)題:

    如何給5歲小孩解釋什么叫DI?

    得分最高的答案是:小孩在餓的時(shí)候只需喊一聲我要吃飯即可,而無(wú)需關(guān)注吃什么飯是怎么來(lái)的等問(wèn)題。

    public class Kid{ ? ?private readonly IFoodSupplier _foodSupplier; ? ?public Kid(IFoodSupplier foodSupplier) ? ?{_foodSupplier = foodSupplier;} ? ?public void HaveAMeal() ? ?{ ? ? ? ?var food = _foodSupplier.GetFood(); ? ? ? ?//eat} }

    DI的背后是一個(gè)DI Container(DI容器)在發(fā)揮作用。DI之所以能夠工作需要兩個(gè)步驟:

  • 將組件注冊(cè)到DI容器中;

  • DI容器統(tǒng)一管理所有依賴關(guān)系,將依賴組件注入到所需要相應(yīng)的組件中;

  • 3.1 組件的注冊(cè)方式

    組件注冊(cè)到DI容器中有3種方式:

  • 通過(guò)XML文件注冊(cè)

  • 通過(guò)Attribute(Annotation)注冊(cè)

  • 通過(guò)DI容器提供的API注冊(cè)

  • .net平臺(tái)中的大多數(shù)DI框架都通過(guò)第三種方式進(jìn)行組件注冊(cè),為了介紹這3種不同的注冊(cè)方式,我們通過(guò)Java平臺(tái)下的Spring框架簡(jiǎn)單介紹:Java中的Spring最早以XML文件的方式進(jìn)行組件注冊(cè),發(fā)展到目前主要通過(guò)Annotation來(lái)注冊(cè)。
    假如我們有CustomerRepository接口和相應(yīng)的實(shí)現(xiàn)CustomerRepositoryImpl,下面用三種不同的方式將CustomerRepository和CustomerRepositoryImpl的對(duì)應(yīng)關(guān)系注冊(cè)在DI容器中:

    public interface CustomerRepository { ?
    ?List<Customer> findAll(); }
    ?
    ?public class CustomerRepositoryImpl implements CustomerRepository { ? ?public List<Customer> findAll() { ? ?
    ? ? ?List<Customer> customers = new ArrayList<Customer>();Customer customer = new Customer("Bryan","Hansen");customers.add(customer); ? ?
    ? ? ?? ?return customers;} }

    3.1.1、xml文件注冊(cè)

    <bean name="customerRepository" class="com.thoughtworks.xml.repository.CustomerRepositoryImpl"/>

    3.1.2、Annotation注冊(cè)

    @Repository("customerRepository")

    public class CustomerRepositoryImpl implements CustomerRepository { ?
    ?public List<Customer> findAll() { ? ? ? //...} }

    3.1.3、通過(guò)Java代碼來(lái)實(shí)現(xiàn)注冊(cè)

    @Configurationpublic class AppConfig { ?
    ?@Bean(name = "customerRepository") ?
    ?public CustomerRepository getCustomerRepository() { ? ?
    ? ?return new CustomerRepositoryImpl();} }

    3.1.4通過(guò)下面的方式從Container來(lái)獲取一個(gè)實(shí)例

    appContext.getBean("customerService", CustomerService.class);

    一旦我們將所有組件都注冊(cè)在容器中,就可以靠DI容器進(jìn)行依賴注入了。

    3.2 依賴注入的三種方式

    3.2.1. 構(gòu)造器注入

    正如上面Kid的實(shí)現(xiàn)一樣,我們通過(guò)構(gòu)造器來(lái)注入IFoodSupplier組件,這種方式也是依賴注入最佳方式。

    3.2.2. 屬性注入

    public class Kid2{ ?

    ?public IFoodSupplier FoodSupplier { get; set; } ?
    ?
    ??public void HaveAMeal() ? ?{ ? ? ?
    ??
    ???var food = FoodSupplier.GetFood(); ? ? ? ?//eat} }

    即通過(guò)一個(gè)可讀寫(xiě)的屬性完成注入,該方案的缺點(diǎn)在于為了達(dá)到依賴注入的目的而破壞了對(duì)象的封裝性,所以不推薦。

    3.2.3 方法注入

    通過(guò)添加方法的參數(shù)來(lái)完成注入,一般來(lái)說(shuō)這種方式都可以通過(guò)構(gòu)造器注入的方式來(lái)替換,所以也不太常用。值得一提的是asp.net core源碼中用到了這種注入方式。

    四、依賴注入實(shí)例

    1、Register Resolve Release Pattern

    下面描述了一個(gè)很簡(jiǎn)單的Console application, 所有的組件都通過(guò)Castle Windsor容器進(jìn)行構(gòu)造器注入:

    //registervar ?container = new WindsorContainer(); container.Register(Component.For<IParser>().ImplementedBy<Parser>()); container.Register(Component.For<IWriter>().ImplementedBy<Writer>()); container.Register(Component.For<Application>());//resolvevar application = container.Resolve<Application>(); application.Execute("hel--lo, wor--ld");//releasecontainer.Release(application);

    這個(gè)例子向我們展示了一個(gè)最簡(jiǎn)單的依賴注入使用方式,register所有組件,resolve客戶端程序,最后的release步驟向我們展示了如果顯示從DI容器得到一個(gè)對(duì)象,應(yīng)該顯示釋放該組件。這一步在大多數(shù)情況下并不是必須的,但是在特定場(chǎng)景下會(huì)發(fā)生內(nèi)存泄漏。

    2、.net平臺(tái)下依賴注入最佳實(shí)踐

    下面的解決方案描述了一個(gè)典型的應(yīng)用程序分層結(jié)構(gòu),該分層結(jié)構(gòu)用來(lái)描述如何使用Catle windsor進(jìn)行依賴注入,注意:這并不是一個(gè)合理的領(lǐng)域驅(qū)動(dòng)案例,例如我將Domain模型引用到了Application或者ApplicationService程序集中。

    處在項(xiàng)目最底層的Repository程序集定義了一組UserRepository及其接口IUserRepository,這樣的一個(gè)組件如何注冊(cè)在Windsor Container中呢?Castle提供了一種叫做WindsorInstaller的機(jī)制:

    public class RepositoryInstaller:IWindsorInstaller{ ? ?public void Install(IWindsorContainer container, IConfigurationStore store) ? ?{container.Register(Component.For<IUserRepository>().ImplementedBy<UserRepository>().LifestyleScoped());} }

    該Installer利用Fluent Api定義了IUserRepository和UserRepository的對(duì)應(yīng)關(guān)系,相對(duì)于Java中的Spring框架提供的代碼注冊(cè)方式,該方案的優(yōu)越之處是顯而易見(jiàn)的。
    另外的重點(diǎn)在于該Installer此時(shí)并沒(méi)有執(zhí)行,只有當(dāng)客戶端調(diào)用此Installer時(shí),該組件才真真注冊(cè)進(jìn)容器。這一點(diǎn)很關(guān)鍵,我們后面還會(huì)提到。

    接下來(lái)的ApplicationService層使用了Repository的抽象,一個(gè)典型的使用片斷如下:

    public class UserApplicationService : IUserApplicationService{ ? ?

    ? private
    readonly IUserRepository _userRepository;

    ? ?public UserApplicationService(IUserRepository userRepository) ?
    ??
    {_userRepository = userRepository;} ?
    ? ?
    ? ??public void Register(User user) ? ?{_userRepository.Save(user);} ? ?//.....}

    我們通過(guò)構(gòu)造器注入的方式注入了IUserRepository,同時(shí),作為Service層,它也擁有自己的Installer:

    public class ApplicationServiceInstaller:IWindsorInstaller{

    ? ? ? ? ?public void Install(IWindsorContainer container, IConfigurationStore store) ? ?{container.Register(Classes.FromThisAssembly().BasedOn<IApplicationService>().WithServiceDefaultInterfaces().LifestyleScoped());} }

    上面的例子示范了如何通過(guò)Castle提供的高級(jí)api來(lái)實(shí)現(xiàn)將該程序集中所有繼承于IApplicationService的組件和其默認(rèn)接口一次性全部注冊(cè)到DI容器中。
    比如UserApplicationService和IUserApplicationService,以及未來(lái)將要實(shí)現(xiàn)的OrderApplicationService以及IOrderApplicationService。

    接下來(lái)到客戶端程序集Application層,Application作為使用ApplicationService程序集的客戶端,他才擁有將組件注冊(cè)進(jìn)DI容器的能力,我們定義一個(gè)ApplicationBootstrap來(lái)初始化DI容器并注冊(cè)組件:

    public class ApplicationBootstrap{ ?

    ? ?public static IWindsorContainer Container { get; private set; } ?
    ?
    ? ?public static IWindsorContainer ?RegisterComponents() ? ?{Container=new WindsorContainer();Container.Install(FromAssembly.This());Container.Install(FromAssembly.Containing<ApplicationServiceInstaller>());Container.Install(FromAssembly.Containing<RepositoryInstaller>()); ? ? ? ?return Container;} }

    注意Container.Install(...)方法將執(zhí)行不同應(yīng)用程序的Installer,此時(shí)組件才真真注冊(cè)進(jìn)DI容器。該實(shí)例展示了如何正確的使用依賴注入框架:

  • 不同的程序集之間通過(guò)接口依賴,符合DIP原則;

  • 通過(guò)依賴注入的方式定義好了可運(yùn)行的流程,但是客戶端可以注冊(cè)不同的組件到DI容器中,符合IoC的思想;

  • 3、如何在asp.net mvc和asp.net webapi使用依賴注入

    本文提供的源碼中所含的WebApplicationSample項(xiàng)目演示了如何通過(guò)自定義WindsorControllerFactory來(lái)實(shí)現(xiàn)mvc的依賴注入,通過(guò)自定義WindsorCompositionRoot實(shí)現(xiàn)web api的依賴注入。

    五、高級(jí)進(jìn)階

    asp.net core實(shí)現(xiàn)了一個(gè)還算簡(jiǎn)單的DI容器DenpendencyInjection,感興趣的同學(xué)可以閱讀其源碼。

    原文地址:http://www.cnblogs.com/richieyang/p/6060160.html


    .NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注

    總結(jié)

    以上是生活随笔為你收集整理的DIP原则、IoC以及DI的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。