zoukankan      html  css  js  c++  java
  • Dependency injection in .NET Core的最佳实践

    我们知道依赖注入(DI)是一种实现对象及其协作者或依赖关系之间松散耦合的技术。 ASP.NET Core包含一个简单的内建容器来支持构造器注入。
    我们试图将DI的最佳实践带到.NET Core应用程序中,这表现在以下方面:

    1. 构造器注入
    2. 注册组件
    3. DI in testing

    构造器注入

    我们可以通过方法注入、属性注入、构造器注入的方式来注入具体的实例,一般来说构造器注入的方式被认为是最好的方式,所以在应用程序中将使用构造器注入,请避免使用别的注入方式。一个构造器注入的例子如:

    public class CharacterRepository : ICharacterRepository
    {
        private readonly ApplicationDbContext _dbContext;
    
        public CharacterRepository(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
    }
    

    注册组件到容器

    在使用DI之前,需要告诉容器组件之间的对应关系,例如:

    container.Register<IAService, AService>();
    

    所以当你使用构造器注入的时候,你告诉构造函数需要注入IAService类型的实例,容器会根据你之前注册的对应关系创建AService的实例。
    看起来一切都很简单,但在实际应用过程中并没有这么简单,试想在一个项目中,组件有成千上万个,这成千上万个组件之间的对应关系怎么样维护?
    一个稍微改进点的策略根据这些组件的职责分类,把某一类组件的对应关系抽取成方法:

    private void RegisterApplicationServices(Container container)
    {
        container.Register<IAApplicationService, AApplicationService>();
        container.Register<IBApplicationService, BApplicationService>();
        //...
    }
    
    private void RegisterDomainServices(Container container)
    {
        container.Register<IADomainService, ADomainService>();
        container.Register<IBDomainService, BDomainService>();
        //...
    }
    
    private void RegisterOtherServices(Container container)
    {
        container.Register<IDataTimeSource, DataTimeSource>();
        container.Register<IUserFetcher, UserFetcher>();
        //...
    }
    

    这两个分类有什么特点呢?第一个方法试图把所有的ApplicationService的组件对应关系汇总在一起,第二个方法试图把所有的DomainService的组件对应关系汇总在一起,比起之前已经有了很大的进步。不过随着组件的增加,你需要不断修改这几个方法。

    基于公共接口来注册组件

    第一个方法已经找到了同一类的组件,既然这些组件的性质是一样的,就可以用同样的接口来表示,定义一个空接口用来表示ApplicationService:

    public interface IApplicationService {}
    public interface IAApplicationService : IApplicationService { //.. }
    public interface IBApplicationService : IApplicationService { //.. }
    

    一旦这些组件有了公共特点,尝试创建下面的扩展:

    container.Register(Classes.FromAssembly().BaseOn<IApplicationService>()
    .WithDefaultInterface());
    

    这句代码的意思是显而易见的,扫描某个程序集,找到所有实现了IApplicationService的类进而把组件的对照关系注册到了容器中。

    当组件拥有多个接口

    类是可以拥有多个接口的,在实际开发中,这样的设计也是很常见的:

    public interface IOptions { //... }
    public interface IAlipayOptions : IOptions { //... }
    public class AlipayOptions: IAlipayOptions { //... }
    

    利用上面介绍的扩展注册所有Options:

    container.Register(Classes.FromAssembly().BaseOn<IOptions>()
    .WithDefaultInterface());
    

    尝试通过下面的构造器注入:

    public AlipayPayment(IAlipayOptions alipayOptions) { //... }
    

    工作的很好,没有问题。但是当我们试图从容器里拿到所有的IOptions类型:

    container.ResolveAll<IOptions>();
    

    你得不到任何IOptions类型的实例,原因在于向容器注册对应关系的过程是一对一的,我们之前的扩展.WithDefaultInterface()只注册了AlipayOptions和IAlipayOptions的关系,如果想通过上面的方式拿到所有继承了IOptions的实例,则需要使用另一个扩展:

    container.Register(Classes.FromAssembly().BaseOn<IOptions>()
    .WithAllInterfaces());
    

    把注册文件放在正确的位置

    我们通过分层的方式隔离了不同职责的程序集,最终Web/API项目将会引用这些低层的程序集。要想把 Web/API启动起来,需要把所有程序集定义的组件注册在Web/API项目的容器中。我们把Web/API这种能够启动的程序集叫做客户端。
    所以一个典型的客户端需要通过下面代码来注册DI容器:

    container.Register(Classes.FromAssembly().BaseOn<IApplicationService>()
    .WithDefaultInterface());
    container.Register(Classes.FromAssembly().BaseOn<IDomainService>()
    .WithDefaultInterface());
    //...
    // 还有其他无法用公共接口表示的组件,这些组件可能来自于低层服务
    container.Register<IDateTimeSource, DateTimeSource>();
    container.Register<IUserFetcher, UserFetcher>();
    //...
    

    这段代码描述了一个现象,Web/API客户端对低层的组件对应关系一清二楚,违反了Tell, Don't Ask Priciple. 正确的做法是:
    Web/API客户端告诉低层组件,帮我安装你所在的程序集中所有的组件对应关系。

    // 安装所有
    services.Install(FromAssembly.Contains<IApplicationService>());
    services.Install(FromAssembly.Contains<IDomainService>());
    services.Install(FromAssembly.Contains<IOtherService>());
    

    具体的组件对应关系应该定义在相应的程序集中。
    这一节的思想都来源于Windsor Castle

    DI in testing

    人们在不断讨论单元测试的各种风格和差异,类似于通过Mock来管理依赖的单元测试被认为是一种反模式。见:To Kill a Mockingtest, 而DI的另一个功能在于便于写出有价值和有效的单元测试。
    当你选择测试一个组件时,实际上要花很多的时间来准备依赖数据,这是显而易见的,因为组件并不是独立存在的。试想如果你能从容器中拿到这个组件,容器就会将所有的依赖关系创建好。
    但是问题来了,比如说你的被测试组件依赖了一个能够给第三方发送请求的组件,这显然并不是你所期望的,你只需要注册一个假的事先准备好的组件即可。
    对ApplicationServiceTests的组件注册如下:

    container.Install(FromAssembly.Contains<FakedComponentsInstaller>());
    //..Register other components that ApplicationService depend on
    

    一个对SearchService的测试如下:

    [Fact]
    public async void WhenInputDataIsValidShouldGetSearchResult()
    {
        //Arrage
        var searchService = _container.Resolve<ISearchService>();
        var searchModel = SearchModelBuilder.Default().Build();
    
        //Act
        var result = await searchService.Search(searchModel);
    
        //Assert
        result.Count.Should().BeGreaterThan(0);
    }
    
  • 相关阅读:
    arm gdbserver armlinuxgdb
    建立非模态对话框与在线程中建立非模态对话框
    AutoLock C++
    Uboot bootcmd 和bootargs
    SetRegistryKey
    飞凌OK6410 uboot支持网络
    uboot 源码修改 bootcmd,IP ,BOOTARGS等参数
    TextBox(richTextBox) 光标和滚动条到文本末尾
    Windbg中使用查找内存并设置访问断点
    opencv EXAMPLES 编译 VS2010 (C++)
  • 原文地址:https://www.cnblogs.com/xiandnc/p/9407856.html
Copyright © 2011-2022 走看看