从哪里加载和存储文件设置?


9

我认为这个问题应该适用于大多数从文件加载设置的程序。我的问题是从编程的角度来看的,实际上这是如何从不同的类和可访问性方面处理从文件中加载设置的问题。例如:

  • 如果程序有一个简单的settings.ini文件,则应将其内容加载到load()类的方法还是构造函数中?
  • 值应该存储在public static变量中,还是应该有static获取和设置属性的方法?
  • 如果文件不存在或无法读取该怎么办?您将如何让程序的其余部分知道它无法获得那些属性?
  • 等等

我希望在这里的正确位置提出这个问题。我想让问题尽可能与语言无关,但我主要关注的是具有继承性的语言,尤其是Java和C#.NET。


1
对于.NET,最好使用App.config和System.Configuration.ConfigurationManager类来获取设置
Gibson 2013年

@Gibson我只是从.NET开始,所以您可以将我链接到该类的任何不错的教程吗?
安迪


@Gibson谢谢您的那些链接。它们将非常有用。
安迪

Answers:


8

这实际上是一个非常重要的问题,并且通常做错了,因为尽管它几乎是每个应用程序的核心部分,但是却没有给予足够的重视。这是我的指南:

包含所有设置的配置类应该只是一个普通的旧数据类型struct / class:

class Config {
    int prop1;
    float prop2;
    SubConfig subConfig;
}

它不需要方法,也不必涉及继承(除非它是您使用语言实现变体字段的唯一选择-请参见下一段)。它可以并且应该使用组合将设置分组为较小的特定配置类(例如,上面的subConfig)。如果以这种方式进行,那么在单元测试和一般应用程序中进行传递将是理想的选择,因为它将具有最小的依赖性。

您可能需要使用变体类型,以防不同设置的配置结构不同。公认的是,当您读取值以将其强制转换为正确的(子)配置类时,需要在某个时候进行动态强制转换,并且毫无疑问,这将取决于另一个配置设置。

通过执行以下操作,您不应该懒于将所有设置作为字段输入:

class Config {
    Dictionary<string, string> values;
};

这很诱人,因为这意味着您可以编写一个通用的序列化类,而不必知道它处理的是什么字段,但这是错误的,稍后我将解释原因。

配置的序列化是在完全独立的类中完成的。无论使用什么API或库来执行此操作,序列化函数的主体都应包含基本上等于从文件中的路径/键到对象上的字段的映射的条目。某些语言提供了很好的自省功能,可以为您提供开箱即用的功能,而其他语言则必须显式地编写映射,但关键是您只需要编写一次映射即可。例如,请考虑我根据c ++ boost program options解析器文档改编而成的摘录:

struct Config {
   int opt;
} conf;
po::options_description desc("Allowed options");
desc.add_options()
    ("optimization", po::value<int>(&conf.opt)->default_value(10);

请注意,最后一行基本上说“优化”映射到Config :: opt,并且还有一个您期望的类型的声明。如果类型不是您期望的类型,或者文件中的参数不是真正的float或int或不存在,则您希望读取配置失败。即,当您读取文件时应该发生故障,因为问题出在文件的格式/验证上,您应该抛出一个例外/返回码并报告确切的问题。您不应将此延迟到以后的程序中。这就是为什么您不应该想像上面提到的那样捕获所有Dictionary样式Conf,并且在读取文件时不会失败-因为强制转换会延迟到需要该值时。

您应该以某种方式使Config类为只读-在创建类并从文件初始化它时,只需设置一次类的内容。如果您需要在应用程序中具有更改的动态设置以及没有更改的const设置,则应该有一个单独的类来处理动态设置,而不是尝试使config类的位不是只读的。

理想情况下,您在程序中的某个位置读取文件,即,您只有一个“ ConfigReader” 实例。但是,如果您正在努力将Config实例传递到需要的地方,那么最好有第二个ConfigReader而不是引入全局配置(我猜这是OP的“静态”含义。 ”),这使我进入下一个观点:

避免使用单调的诱人警笛声:“我省去了您不得不传递该类的麻烦,您所有的构造函数都将变得整洁可爱。继续,这将非常容易。” 真理具有精心设计的可测试体系结构,您几乎不需要将Config类或其中的一部分传递给应用程序的许多类。您将在顶级类,main()函数或其他任何东西中找到的内容,将conf解译为单个值,并将其作为参数提供给组件类,然后将它们放在一起(手动依赖注射)。单例/全局/静态conf将使应用程序的单元测试更加难以实现和理解-例如,它将使新开发人员与您的团队混淆,他们不知道他们必须设置全局状态来测试事物。

如果您的语言支持属性,则应将其用于此目的。原因是这意味着添加依赖于一个或多个其他设置的“派生”配置设置非常容易。例如

int Prop1 { get; }
int Prop2 { get; }
int Prop3 { get { return Prop1*Prop2; }

如果您的语言本身不支持属性惯用语,则可以使用一种变通方法来实现相同的效果,或者您只需创建一个提供奖励设置的包装器类即可。如果您不能以其他方式赋予属性的好处,那么手动编写并仅出于取悦某些面向对象的目的而使用getter / setter就是浪费时间。一块朴实的旧田野会更好。

您可能需要一个系统来合并并按优先级从不同位置获取多个配置。优先顺序应明确定义,并为所有开发人员/用户所理解,例如,考虑Windows注册表HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE。您应该使用此功能样式,以便可以将配置保持只读状态,即:

final_conf = merge(user_conf, machine_conf)

而不是:

conf.update(user_conf)

如果您选择的框架/语言提供了自己的内置的,众所周知的配置机制,那么我最后应该补充一点,您应该考虑使用它而不是自己滚动使用的好处。

所以。要考虑很多方面-正确处理它会深刻影响您的应用程序体系结构,减少错误,使事情易于测试,并迫使您在其他地方使用良好的设计。


+1感谢您的回答。以前,当我存储用户设置时,我只是从文件中读取诸如a这样的文件,.ini以便于人类阅读,但是您是否建议我应该序列化带有变量的类?
安迪

.ini易于解析,并且还有许多API包括.ini作为可能的格式。我建议您使用这样的API来帮助您完成繁琐的文本解析工作,但最终结果应该是您已经初始化了POD类。我使用术语序列化的一般含义是从某种格式复制或读取类的字段,而不是直接将类序列化为二进制或从二进制序列化的更具体的定义(如通常所说的java.io.Serializable )。
本尼迪克特

现在我了解得更好了。因此,从本质上讲,您是说在一个类中包含所有字段/设置,而在另一个类中设置文件中该类的值?然后,我应该如何从其他类的第一类中获取值?
安迪

就事论事。如果您的某些顶级类依赖太多参数,则可能只需要引用传递给它们的整个Config实例。其他类可能只需要一个或两个参数,但是不需要将它们耦合到Config对象(使它们更易于测试),只需将几个参数传递给应用程序即可。如我的回答中所述,如果您使用面向可测试/ DI的体系结构进行构建,那么在需要的地方获取值通常不会很困难。使用全局访问会带来危险。
本尼迪克特

因此,如果不需要涉及继承,您如何建议将config对象传递给类?通过构造函数?因此,在程序的主方法中,将启动读取器类,该类将值存储在Config类中,然后main方法将Config对象或单个变量传递给其他类?
安迪

4

总的来说(我认为),最好让应用程序处理配置的存储方式,然后将配置传递到模块中。这允许灵活性如何保存设置,这样就可以针对文件或web服务或数据库或...

另外,它把“事情失败时会发生什么”的负担加到应用程序上,谁最了解失败意味着什么。

而且,当您只需传递配置对象而不需要接触文件系统或处理由静态访问引入的并发问题时它就使单元测试变得更加容易。


谢谢您的回答,但我不太理解。我在谈论编写应用程序时,因此没有任何要处理的配置,因此,作为一名程序员,我确实知道失败意味着什么。
安迪

@andy-可以,但是如果模块直接访问配置,则它们不知道它们正在使用的上下文,因此它们无法确定如何处理配置问题。如果应用程序正在执行加载,则由于它知道上下文,因此可以处理任何问题。一些应用可能想要故障转移到默认值,另一些则想要中止操作,等等
。– Telastyn

只是在这里澄清一下,您是按模块指的是类,还是程序的实际扩展?因为我要问的是,将代码实际存储在主程序中的最佳存储位置是什么,以及如何允许其他类访问这些设置。因此,我想加载配置文件,然后将设置存储在某处/以某种方式存储,因此我不必继续引用该文件。
安迪

0

如果使用.NET对类进行编程,则可以使用不同的选项,例如资源,web.config或自定义文件。

如果使用Resources或web.config,则数据实际上存储在XML文件中,但是加载速度更快。

从这些文件中检索数据并将它们存储在另一个位置,就像重复使用内存一样,因为默认情况下这些文件已加载到内存中。

对于任何其他文件或编程语言,Benedict的上述回答将起作用。


多谢您的答复,对不起,答复缓慢。使用资源将是一个不错的选择,但我刚刚意识到,对于我似乎打算开发其他语言的相同程序,这可能不是最佳选择,所以我宁愿拥有XML或JSON文件,但在我自己的班级中读取它,以便可以使用其他编程语言读取相同的文件。
安迪
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.