这实际上是一个非常重要的问题,并且通常做错了,因为尽管它几乎是每个应用程序的核心部分,但是却没有给予足够的重视。这是我的指南:
包含所有设置的配置类应该只是一个普通的旧数据类型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)
如果您选择的框架/语言提供了自己的内置的,众所周知的配置机制,那么我最后应该补充一点,您应该考虑使用它而不是自己滚动使用的好处。
所以。要考虑很多方面-正确处理它会深刻影响您的应用程序体系结构,减少错误,使事情易于测试,并迫使您在其他地方使用良好的设计。