使用依赖注入将配置代码排除在逻辑代码之外的方法

Posted

技术标签:

【中文标题】使用依赖注入将配置代码排除在逻辑代码之外的方法【英文标题】:Ways of keeping configuration code out of logic code using Dependency Injection 【发布时间】:2011-11-09 11:19:49 【问题描述】:

如何使用设置(ApplicationSettingsBase)和依赖注入将所有配置文件代码排除在我的逻辑代码之外?

配置是指客户特定的配置文件。

我真的必须在每次需要时都注入一个配置类,还是有其他模式?

如果能得到一些示例代码就太好了!

样品:

静态配置:

public static class StaticConfiguration

    public static bool ShouldApplySpecialLogic  get; set; 
    public static string SupportedFileMask  get; set; 


public class ConsumerOfStaticConfiguration

    public void Process()
    
        if (StaticConfiguration.ShouldApplySpecialLogic)
        
            var strings = StaticConfiguration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            

            
        
    

非静态配置:

public interface IConfiguration

    bool ShouldApplySpecialLogic  get; set; 
    string SupportedFileMask  get; set; 


public class Configuration : IConfiguration

    public bool ShouldApplySpecialLogic  get; set; 
    public string SupportedFileMask  get; set; 


public class Consumer

    private readonly IConfiguration _configuration;

    public Consumer(IConfiguration configuration)
    
        _configuration = configuration;
    

    public void Process()
    
        if (_configuration.ShouldApplySpecialLogic)
        
            var strings = _configuration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            

            
        
    

具有非静态配置的静态上下文:

public static class Context

    public static IConfiguration Configuration  get; set; 


public class ConsumerOfStaticContext

    public void Process()
    
        if (Context.Configuration.ShouldApplySpecialLogic)
        
            var strings = Context.Configuration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            

            
        
    

【问题讨论】:

你想要的是一个控制反转容器 @Nico 我想要得到的是关于使用控制容器的反转将逻辑代码与配置分离的解释。 我写了一篇博文,解释了我们如何以及为什么使用 StructureMap 将我们的配置与我们的逻辑分开:lostechies.com/joshuaflanagan/2009/07/13/… 该博文中描述的功能现在可以在 FubuCore 实用程序库中使用(你可以得到它通过nuget):github.com/DarthFubuMVC/fubucore/tree/master/src/FubuCore/… 【参考方案1】:

在我的应用程序中,我使用 IoC 执行您在上面所做的操作。也就是说,让我的 IoC 容器(也包括结构图)将 IApplicationSettings 注入到我的类中。

例如,在 ASP.NET MVC3 项目中,它可能如下所示:

Public Class MyController
    Inherits Controller

    ...
    Private ReadOnly mApplicationSettings As IApplicationSettings

    Public Sub New(..., applicationSettings As IApplicationSettings)
        ...
        Me.mApplicationSettings = applicationSettings
    End Sub

    Public Function SomeAction(custId As Guid) As ActionResult
         ...

         ' Look up setting for custId
         ' If not found fall back on default like
         viewModel.SomeProperty = Me.mApplicationSettings.SomeDefaultValue

         Return View("...", viewModel)
    End Function
End Class

我的IApplicationSettings 实现从应用程序的.config 文件中提取了大部分内容,并且其中还有一些硬编码值。

我的示例不是逻辑流控制(如您的示例),但如果是的话,它的工作原理是一样的。

另一种方法是执行服务定位器类型模式,您可以在其中要求依赖注入容器即时为您获取配置类的实例。服务位置is considered an anti-pattern generally,但可能仍然对您有用。

【讨论】:

您如何看待“具有非静态配置的静态上下文”? @Rookian 似乎也是一个可测试的解决方案,并且完全避免了 DI。它可能不像需要的那样“灵活”。使用 DI,您的 IoC 容器可以为不同的类提供不同的实现(如果需要);在使用静态路由时,您几乎被锁定在配置的一种实现中。在你的情况下可能不是问题,只是想我会提到它。【参考方案2】:

一种方法是像您发布的那样注入配置接口。以下是其他几种方法。

暴露二传手

class Consumer

    public bool ShouldApplySpecialLogic  get; set; 

    ...

在组合根目录中,您可以读取配置文件或对其进行硬编码。 Autofac 示例:

builder.RegisterType<Consumer>().AsSelf()
    .OnActivated(e => e.Instance.ShouldApplySpecialLogic = true);

这可能仅在您有良好的默认值时才可取

构造函数注入

public class Server

    public Server(int portToListenOn)  ... 

在组合根中:

builder.Register(c => new Server(12345)).AsSelf();

【讨论】:

我害怕在使用构造函数注入时有很多构造函数参数。 Setter 注入可以避免这种情况,但是还有一种获取依赖项的新方法,我认为这会使它变得更加复杂(混合构造函数和 setter 注入。【参考方案3】:

需要认识到的重要部分是,配置只是驱动应用程序行为的多种价值来源之一。

第二个选项(非静态配置)是最好的,因为它使您能够完全将使用者与配置值的来源解耦。但是,接口不是必需的,因为配置设置通常最好建模为 值对象

如果您仍想从配置文件中读取值,您可以从应用程序的Composition Root 中执行此操作。使用 StructureMap,它可能看起来像这样:

var config = (MyConfigurationSection)ConfigurationManager.GetSection("myConfig");

container.Configure(r => r
    .For<Consumer>()
    .Ctor<MyConfigurationSection>()
    .Is(config));

【讨论】:

Seeman,使用你的方法会很麻烦,因为我有 100 多个不同的消费者。所以对于每个消费者,我都必须配置我的 IoC 注册,不是吗? 使用约定。对于 StructureMap,入口点是 Scan 方法。 谢谢 :) 我会看看并考虑你的方法。【参考方案4】:

配置类降低了消费者的凝聚力并增加了耦合度。这是因为可能有许多设置与您的类所需的一两个无关,但为了满足依赖关系,您的 IConfiguration 实现必须为所有访问器提供值,即使是不相关的访问器.

它还将您的课程与基础架构知识相结合:诸如“这些值一起配置”之类的细节会从应用程序配置中渗出到您的课程中,从而增加了受无关系统更改影响的表面积。

共享配置值最简单、最灵活的方法是使用构造函数注入值本身,将基础架构问题外部化。但是,在对另一个答案的评论中,您表示您害怕拥有大量构造函数参数,这是一个有效的担忧。

要认识到的关键点是原始依赖和复杂依赖之间没有区别。无论您依赖的是整数还是接口,它们都是您不知道并且必须被告知的东西。从这个角度来看,IConfigurationIDependencies 一样有意义。大的构造函数表明一个类有太多的责任,不管参数是原始的还是复杂的。

考虑像对待任何其他依赖项一样对待 intstringbool。它会让你的课程更简洁、更专注、更能抵抗变化,并且更容易进行单元测试。

【讨论】:

我认为这是最好的答案,因为它认识到类不需要知道IConfiguration,而只需要知道它们的实际依赖关系是什么。您可能有一天会以不同的方式配置一个类的两个实例。例如“如果我们使用某些安装首选的 co-confabulator 算法而不是其他安装首选的 reverse-hindle***,结果有何不同?” 我说对了吗,您不会使用复杂类型,而是使用简单类型,如 int、string 和 bool?如果是这样,如果您有 5 个不同的配置参数和 2 个依赖项怎么办。这会使你的构造函数膨胀。对于需要进行一些配置的每种情况,也许可以使用特定的复杂类型。 FileParserConfiguration、BootConfiguration、DatabaseConfiguration 之类的东西。我仍然不确定我应该使用什么:) @Rookian:我认为这种情况有 7 个依赖项。与任何具有大量依赖项的类一样,需要进行一些重构以减少类的责任。构造函数膨胀是类做太多的症状。它与原始值没有任何关系。 @Rookian:换句话说:将构造函数值分组到 IConfiguration 类型下可能会减少构造函数参数的数量,但不会降低复杂性,这是构造函数膨胀背后的真正危险。跨度> @Rookian:我同意参数对象是对值进行分组的有效且有用的方法。关键因素是为什么它们被分组:配置是概念的技术组织,而参数对象倾向于围绕问题域。如果两个值仅仅因为它们是可配置的而被分组,那并不能有效地传达它们的目的。例如,如果要对主机名和端口进行分组,IServerContextIConfiguration 更具表现力。分组应该有利于意图而不是机制。

以上是关于使用依赖注入将配置代码排除在逻辑代码之外的方法的主要内容,如果未能解决你的问题,请参考以下文章

Spring依赖注入的简化配置

依赖注入 gin项目的目录结构说明

Android依赖注入Dagger的使用和源码解析(上篇)

C#单元测试--如何使用moq.mock进行依赖注入

我可以将依赖项注入迁移(使用 EF-Core 代码优先迁移)吗?

AngularJS:依赖注入