依赖注入和AppSettings

dev*_*ium 11 c# oop dependency-injection application-settings

假设我正在为我的应用程序定义一个浏览器实现类:

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath = @"C:\Program Files\...\...\ie.exe";

    ...code that uses executablePath
}
Run Code Online (Sandbox Code Playgroud)

乍一看这可能看起来是个好主意,因为executablePath数据接近将使用它的代码.

当我尝试在另一台具有外语操作系统的计算机上运行相同的应用程序时executablePath会出现问题:将具有不同的值.

我可以通过AppSettings单例类(或其等价物)来解决这个问题,但是没有人知道我的类实际上依赖于这个AppSettings类(它违背了DI ideias).它也可能给单元测试带来困难.

我可以executablePath通过构造函数传入来解决这两个问题:

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath;

    public InternetExplorerBrowser(string executablePath) {
        this.executablePath = executablePath;
    }
}
Run Code Online (Sandbox Code Playgroud)

但这会引起我的问​​题Composition Root(启动方法将执行所有需要的类连接),因为那个方法必须知道如何连接并且必须知道所有这些小设置数据:

class CompositionRoot {
    public void Run() {
        ClassA classA = new ClassA();

        string ieSetting1 = "C:\asdapo\poka\poskdaposka.exe";
        string ieSetting2 = "IE_SETTING_ABC";
        string ieSetting3 = "lol.bmp";

        ClassB classB = new ClassB(ieSetting1);
        ClassC classC = new ClassC(B, ieSetting2, ieSetting3);

        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

这很容易变成一团糟.

我可以通过传递表单的接口来解决这个问题

interface IAppSettings {
    object GetData(string name);
}
Run Code Online (Sandbox Code Playgroud)

所有需要某种设置的类.然后,我可以将其实现为具有嵌入其中的所有设置的常规类,或者从XML文件读取数据的类.如果这样做,我应该为整个系统有一个通用的AppSettings类实例,还是有一个AppSettings类与每个可能需要一个类的类相关联?这当然看起来有点矫枉过正.此外,将所有应用程序设置放在同一个位置,可以轻松查看并查看在尝试将程序移动到不同平台时我需要做的所有更改.

什么是解决这种常见情况的最佳方法?

编辑:

那么使用IAppSettings硬编码的所有设置怎么样?

interface IAppSettings {
    string IE_ExecutablePath { get; }
    int IE_Version { get; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

这将允许编译时类型安全.如果我看到界面/具体类增长太多,我可以创建表单的其他较小的接口IMyClassXAppSettings.在医疗/大型项目中承受的负担是否过重?

我还阅读了关于AOP及其处理跨领域问题的优势(我想这是一个).难道它也不能提供这个问题的解决方案吗?也许标记这样的变量:

class InternetExplorerBrowser : IBrowser {
    [AppSetting] string executablePath;
    [AppSetting] int ieVersion;

    ...code that uses executablePath
}
Run Code Online (Sandbox Code Playgroud)

然后,在编译项目时,我们也有编译时安全性(编译器检查我们实际上是否实现了编组数据的代码.当然,这会将我们的API与这个特定的方面联系起来.

Bry*_*tts 15

各个类应尽可能不受基础设施的影响 - 构造类似于IAppSettings,IMyClassXAppSettings并将[AppSetting]组合细节放到类中,这些类最简单,实际上只依赖于原始值,例如executablePath.依赖注入的艺术是关注的因素.

我已经使用Autofac实现了这个确切的模式,它具有类似于Ninject的模块,并且应该产生类似的代码(我意识到问题没有提到Ninject,但是OP在评论中做了).

模块按子系统组织应用程序 模块公开子系统的可配置元素:

public class BrowserModule : Module
{
    private readonly string _executablePath;

    public BrowserModule(string executablePath)
    {
        _executablePath = executablePath;
    }

    public override void Load(ContainerBuilder builder)
    {
        builder
            .Register(c => new InternetExplorerBrowser(_executablePath))
            .As<IBrowser>()
            .InstancePerDependency();
    }
}
Run Code Online (Sandbox Code Playgroud)

这使组合根存在同样的问题:它必须提供值executablePath.为了避免配置,我们可以编写一个自包含的模块来读取配置设置并将它们传递给BrowserModule:

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var executablePath = ConfigurationManager.AppSettings["ExecutablePath"];

        builder.RegisterModule(new BrowserModule(executablePath));
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以考虑使用自定义配置部分而不是AppSettings; 更改将本地化到模块:

public class BrowserSection : ConfigurationSection
{
    [ConfigurationProperty("executablePath")]
    public string ExecutablePath
    {
        get { return (string) this["executablePath"]; }
        set { this["executablePath"] = value; }
    }
}

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var section = (BrowserSection) ConfigurationManager.GetSection("myApp.browser");

        if(section == null)
        {
            section = new BrowserSection();
        }

        builder.RegisterModule(new BrowserModule(section.ExecutablePath));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的模式,因为每个子系统都有一个独立的配置,可以在一个地方读取.这里唯一的好处是一个更明显的意图.string但是,对于非价值或复杂的模式,我们可以放弃System.Configuration繁重的工作.