问题:配置蔓延随着时间的推移,程序获得了功能和选项。当他们连接到外部系统和服务(例如数据库、事件代理、云/Web 服务)时,他们还必须为这些服务保留越来越多的配置和凭据。
在运行时存储此信息的传统位置是全局变量和操作系统环境变量。两者都很烂。配置数据在逻辑上是“全局环境”或应用程序运行的上下文,但您不能轻易依赖全局或环境变量。
另一种传统机制,配置文件——无论是 XML、INI、.properties 还是其他——帮助在运行之间存储配置数据,但在程序代码或执行期间不做任何组织配置/选项的操作。
您可以通过将选项添加到应用程序类的属性中来稍微清理一下。这是传统的“下一步”。不幸的是,它可能需要很多前期“什么去哪里?” 思维。海事组织,超过它的价值。即使你做出正确的选择,它也不会持久。如果你有一个功能相当丰富的应用程序,随着时间的推移,选项和设置的数量将变得不堪重负。您将花费大量时间手动编写对象构造函数中的默认值和选项以及其他方法的参数/代码。试图在类之间完美地划分配置不仅会耗费精力,还会导致高度相互依赖的类。这么多干净的封装!
我经常把头撞在这堵墙上,尤其是在针对所有代码都具有“合理”或“智能”默认值和行为的代码时,它允许用户随时覆盖默认值,并且提供了一个简单的界面不需要了解应用程序类和组件的完整相互作用即可使用其服务。
解决方案:委托给一个配置对象我发现的最佳解决方案是将选项/配置数据封装到它自己的指定对象中。描述这种模式的一种奇特方式:对于配置、设置和选项数据,使用委托而不是继承或组合。
如何构建配置映射取决于您使用的语言。在许多语言中,构建自己的Config
对象会给您一个漂亮的“外观”:
if opts.verbose:
print "..."
我发现它比访问属性的更明确的“getter”opts.get("verbose")
或“indexer”方式更具可读性。opts['verbose']
但是你通常不必自己制作Config
类,它基本上只是一个映射。
●简单方法● 使用通用映射:例如在 Python 中 a dict
,在 Perl 中 a %hash
,在 Java 中 aDictionary
或HashMap
。更好的是,这些扩展是为配置数据设计的,或者特别适用于配置数据。例如,在 Python 中,我使用stuf和TreeDict来实现它们的简单点访问和其他不错的属性。在 Java 中,Properties
是一个类似的 specific-for-configs 扩展。例如:
from stuf import stuf # stuf has attributes!
opts = stuf(
show_module=False, # comment explaining what show_module means
where=True, # ...
truncate=False, # ...
everything=False, # ...
allvars=False, # ...
allkeys=False, # ...
yaml=False, # ...
on=True, # ...
ret=None, # ...
)
if opts.truncate:
...
这样,您的所有配置和选项数据都在一个地方,可以整齐地访问,并与它并排使用的所有其他程序、类、实例和函数/方法数据清晰地划分出来。随着程序的发展,这有助于随着时间的推移保持清晰。您可以快速确定“这部分是核心数据吗?还是与处理核心数据的上下文有关?”
为了使事情变得更好,如果您从配置文件中预加载配置数据,请将这些值直接加载或复制到您的配置对象中。如果您从命令行获取参数,请将这些值直接加载或复制到您的配置对象中。现在,您拥有了所有“用户希望我做什么,有哪些选项和设置?”的统一来源。信息。
TL;DR - 90% 的应用程序或服务只需简单的配置/选项映射即可。以下所有内容均适用于高级用例。因为这是一个设计/模式问题,这就是为什么这种方法不是一次性的,而是扩展到连续更复杂/复杂的用例。
● Per-Instance Config ● 您可以拥有多个级别的配置/选项数据。最常见的用途是在类或模块级别设置默认值,然后为每个实例设置不同的选项。服务器应用程序可能每个用户都有一个实例,每个用户/实例都需要自己的自定义设置。配置映射在实例创建/初始化时自动或显式复制。
●●多个配置对象●● 如果有意义的话,您可以将配置/选项数据划分为多个配置对象。例如,您可以对用于数据检索的选项与用于数据格式化的选项进行分区。您可以在设计开始时执行此操作,但不必这样做。您可以从一个单一的配置对象开始,然后随着时间的推移进行重构(通常,当您开始重构底层函数时)。显然,您不想“发疯”地添加配置对象,但是您可以拥有一些而不增加太多程序复杂性。如果您对配置对象进行分区,则可以通过单个 API 代理多个配置“域”——在内部为您提供质量信息分解,但外观非常简单。
◆ Chain Gang ◆ 比为每个实例复制配置数据更优雅:使用可链接或分层映射(例如在 Python 中ChainMap
),让您可以将一个映射的值与另一个映射的值“叠加”(类似于“写时复制”方案) ,或“联合”和“半透明”文件系统)。然后实例选项直接引用类/默认选项——除非它们被明确设置,在这种情况下它们是特定于实例的。优点:如果在程序执行期间更改了类/默认/全局设置,后续实例方法调用将“看到”更改的默认值并使用它们(只要它们没有在实例级别被覆盖)。
◆◆瞬态配置◆◆ 如果您需要“即时”更改的配置/选项——比如说给定方法调用的范围——该方法可以扩展实例选项链式映射。在 Python 中,就是ChainMap.new_child()
这样。这听起来很复杂,但就方法代码而言,它非常简单。仍然只有一个配置对象可以引用,无论它说什么是选项,使用它。
◆◆◆任意持续时间叠加◆◆◆ 方法调用的时间范围没有什么神奇之处。通过适当的设置,任何级别的配置都可以根据需要暂时叠加。例如,如果在程序运行期间有一段时间您想打开调试、日志记录或分析,您可以随时打开和关闭它——仅针对某些实例,或同时针对所有实例。这种hors 类别 的用法需要一个Config
稍微超出库存的对象ChainMap
——但不会太多(基本上只是链映射的句柄)。
Happily, most code doesn't come close to needing these "black diamond" levels of config sophistication. But if you want to go three or four levels deep, delegating to separate config objects will take you there in a logical way that keeps code clean and orderly.