如何设计一个具有多种配置选项的程序?
假设我有一个程序,它有很多配置选项。用户可以在一个配置文件里指定这些选项。我的程序可以解析这个配置文件,但我该如何在内部存储和传递这些选项呢?
在我的情况下,这个软件是用来进行科学模拟的。大约有200个选项,其中大多数都有合理的默认值。通常情况下,用户只需要指定十几个选项。我面临的困难是如何设计我的内部代码。许多需要构造的对象依赖于很多配置选项。例如,一个对象可能需要几个路径(用于存储数据的地方),一些需要传递给它调用的算法的选项,还有一些是对象本身直接使用的选项。
这就导致了对象在构造时需要传入非常多的参数。此外,由于我的代码库正在积极开发中,逐层传递一个新的配置选项到需要它的地方是非常麻烦的。
一种避免这种麻烦的方法是使用一个全局配置对象,可以在代码的任何地方自由使用。但我并不特别喜欢这种方法,因为这会导致函数和类不接受任何参数(或者只接受一个参数),读者不容易理解这个函数或类处理的数据是什么。这也阻碍了代码的重用,因为所有代码都依赖于一个巨大的配置对象。
有没有人能给我一些关于这个程序应该如何结构化的建议?
下面是我所说的配置选项传递风格的一个例子:
class A:
def __init__(self, opt_a, opt_b, ..., opt_z):
self.opt_a = opt_a
self.opt_b = opt_b
...
self.opt_z = opt_z
def foo(self, arg):
algo(arg, opt_a, opt_e)
这是全局配置风格的一个例子:
class A:
def __init__(self, config):
self.config = config
def foo(self, arg):
algo(arg, config)
这些例子是用Python写的,但我的问题适用于任何类似的编程语言。
4 个回答
你可以传递一个配置解析的类,或者写一个类来包装它,这样就能智能地提取你需要的选项。
Python的标准库里有一个叫configparser
的东西,它可以让你使用一种叫做映射协议的方式来访问INI格式配置文件的各个部分和选项。这样,你就可以像使用字典一样直接获取你的选项。
myconf = configparser.ConfigParser()
myconf.read('myconf.ini')
what_to_do = myconf['section']['option']
如果你想通过属性的方式明确提供选项,可以创建一个类,重写__getattr__
方法:
class MyConf:
def __init__(self, path):
self._parser = configparser.ConfigParser()
self._parser.read('myconf.ini')
def __getattr__(self, option):
return self._parser[{'what_to_do': 'section'}[option]][option]
myconf = MyConf()
what_to_do = myconf.what_to_do
有几个设计模式可以帮助你
- 原型模式
- 工厂模式和抽象工厂模式
可以把这两种模式用在配置对象上。每个方法会接收一个配置对象,然后根据需要使用里面的内容。同时,考虑把配置参数进行逻辑分组,想想有没有办法减少输入的数量。
伪代码
// Consider we can run three different kinds of Simulations. sim1, sim2, sim3
ConfigFactory configFactory = new ConfigFactory("/path/to/option/file");
....
Simulation1 sim1;
Simulation2 sim2;
Simulation3 sim3;
sim1.run( configFactory.ConfigForSim1() );
sim2.run( configFactory.ConfigForSim2() );
sim3.run( configFactory.ConfigForSim3() );
在每个工厂方法内部,可以从一个原型对象创建配置,这个原型对象包含了所有“合理”的默认值,而选项文件只包含那些与默认值不同的部分。这样做的同时,应该有清晰的文档说明这些默认值是什么,以及在什么情况下人(或其他程序)可能想要更改它们。
** 编辑: ** 还要考虑每个工厂返回的配置是整体配置的一个子集。
matplotlib是一个功能强大的库,里面有很多配置选项。它使用一个叫做rcParams的模块来管理所有的默认设置。rcParams会把所有的默认参数保存在一个字典里。
每个函数都会从关键字参数中获取这些选项:
比如:
def f(x,y,opt_a=None, opt_b=None):
if opt_a is None: opt_a = rcParams['group1.opt_a']