使用静态类来方便访问应用设置是否可以接受

本文关键字:是否 设置 应用 静态类 方便 访问 | 更新日期: 2023-09-27 17:53:05

我在我的应用程序的app.config文件中有一些键,其中包含电子邮件设置。我目前正在为工作开发的应用程序有几个地方,我需要发送电子邮件(主要是在错误检查,让我们知道一些不好的事情发生)。为了更容易地抓住我需要它的设置(而不是回到app.config中查找要在ConfigurationManager.AppSettings["SomeKey"].ToString()中使用的文本字符串),我创建了一个简单的静态类来容纳一些返回我需要的值的只读属性。下面是基本结构

internal static Class myClass
{
    internal static string setting1
    {
        get { return ConfigurationManager.AppSettings["SomeKey"].ToString(); }
    }
    internal static string setting2
    {
        get { return ConfigurationManager.AppSettings["SomeOtherKey"].ToString(); }
    }
}

我这样做可以接受吗?我找不到问题,但忍不住想我错过了什么。

额外的信息:

我知道简单地使用设置的方法。设置文件。我无法使用设置。由于我工作的地方的代码策略,这将使我这样做的方式变得不必要。

编辑:我所说的"可接受的"是指它是否被普遍采用或被认为是一个好主意?我意识到整个社会不能推测我的工作的可接受性。

使用静态类来方便访问应用设置是否可以接受

效果还不错,但稍加调整就会更好。(好吧,也许不是真的小。)

如果你的类依赖于一个静态类,而这个静态类依赖于AppSettings,那么你的类仍然耦合到AppSettings。换句话说,它们没有其他方法获得设置。如果要对类进行单元测试,这是一个挑战。这意味着您的单元测试项目必须具有相同的<appSettings>部分。但是,如果您希望两个测试对一个设置使用两个不同的值,该怎么办?这是不可能的。或者,如果你有一个需要设置的类,几年后你想在ASP中使用它,该怎么办?. NET Core应用程序,没有web.config?那就根本行不通了。

为了避免这种情况,你可以这样做:

public interface IMySettings
{
    string Setting1 {get;}
    string Setting2 {get;}
}
public class MyConfigurationSettings : IMySettings
{
    public string Setting1
    {
        get { return ConfigurationManager.AppSettings["SomeKey"].ToString(); }
    }
    public string Setting2
    {
        get { return ConfigurationManager.AppSettings["SomeOtherKey"].ToString(); }
    }
}

然后在需要设置的类中:

public class ClassThatNeedsSettings
{
    private readonly IMySettings _settings;
    public ClassThatNeedsSettings(IMySettings settings)
    {
        _settings = settings;
    }
}

然后,当你创建ClassThatNeedsSettings的实例时,你传递一个实现IMySettings的类的实例,这个类将使用它来检索设置。当应用程序运行时,您传入MyConfigurationSettings,以便您的值来自AppSettings。但是ClassThatNeedsSettings从来不知道。它只知道它正在使用IMySettings的实例。

这叫做"依赖注入"。ClassThatNeedsSettings依赖于IMySettings,所以你将它"注入"到构造函数中。这样ClassThatNeedsSettings就得到了它所需要的。它不负责创造它。

如果您想进行单元测试,您可以"模拟"IMySettings。也就是说,您可以创建实现接口的其他类,并使用它们传入您想要测试的任何值。甚至有像Moq这样的工具可以帮助你创建这些类。

通常情况下,如果你使用依赖注入,你还会使用像Windsor, Unity, Autofac这样的框架来为你管理创建对象。感觉有点像在最后引入诱饵交换,因为它需要更多的学习,可能还需要改变应用程序的配置方式。但这就是我们使用它的原因,为了防止一个类对另一个类有绝对依赖,这使得它更不灵活,更难以测试。

下面是一个可测试代码的例子,它解决了评论者所关心的问题:

interface IEmailSettings {
    string Server { get; }
    string Sender { get; }
    string[] Recipients { get; }
}

如果你将你的设置存储在app.config中,使用如下:

class AppConfigEmailSettings : IEmailSettings {
    public AppConfigEmailSettings() {
        this.Server = ConfigurationManager.AppSettings["server"];
        this.Sender = ConfigurationManager.AppSettings["sender"];
        this.Recipients = ConfigurationManager.AppSettings["recipients"].Split(';');
    }
    public string Server { get; private set; }
    public string Sender { get; private set; }
    public string[] Recipients { get; private set; }
}

如果您将设置存储在数据库中,请使用:

class DatabaseEmailSettings : IEmailSettings {
    public DatabaseEmailSettings(string connectionString) {
        //code to connect to database and retrieve settings
    }
    //you can use the same fields and properties from AppConfigEmailSettings
}
对于测试,您可以使用这样的内容:
class MockSettings : IEmailSettings {
    public string Server { get { return "localhost"; } }
    public string Sender { get { return "sender@example.com" } }
    public string[] Recipients { get { return new string[] { "r1@example.com" }; } }
}

你懂的。这段代码比您的代码更容易测试。此外,如果您将IEmailSettings注入到发送电子邮件的代码中,您可以通过更改整个应用程序中的一行代码轻松更改存储电子邮件设置的方式。这是将IEmailSettings对象实例化为AppConfigEmailSettingsDatabaseEmailSettings或其他对象的行。

这里没有人能真正定义你的情况下什么是"可接受的"或"不可接受的"。我认为你真正想问的是这是否是个好主意,或者它可能在哪里出现问题。

简短的回答是,对于一个简单的解决方案,可能是可以接受的,但对于任何复杂的解决方案,这是一个坏主意。静态类使得单元测试非常困难(或者不可能)。相反,正如您在评论中提到的,更好的选择是定义一个具有GetSettings方法的接口。您可以使用一个类(不是静态的)来实现它,该类只封装对ConfigurationManager的调用。AppSettings .

任何需要访问settings的类都应该将这个接口传递给构造函数。您可以使用IoC容器或创建类的新实例来传入(同样取决于复杂性),但更重要的是,您现在可以编写单元测试并传入带有单元测试自定义设置的类。