7

我正在创建一个程序,该程序需要设置一些选项值以及图像文件的一些路径、SQLite 数据库的路径、有关各种按钮上的文本的一些信息、有关要使用的数据库的信息(例如 SQLite / MySQL) , ETC。

到目前为止,我有一个帮助类,其中我将所有内容都设为静态,并从程序中的任何地方访问它,但它变得一团糟,我读到这是一种不好的做法,因为它不遵循面向对象的编程指导方针。

所以我所做的是使该类成为一个单例,并在我的控制器对象中引用它。要访问我的选项,我现在调用 controller.getOptionName 而不是直接调用 Helper.getOptionName。

我有一种感觉,我正在以错误的方式处理这个问题,特别是因为从我所读到的内容来看,如果我的许多对象都依赖于一个类(辅助类),那么我没有足够地分离所有东西。

我不知道我应该做什么,是否有一个“标准”来保存我的所有选择?我考虑过使用 XML 文件或类似的东西,但我最终不得不从任何地方访问它,所以感觉这会产生同样的问题。

4

3 回答 3

9

问题:配置蔓延随着时间的推移,程序获得了功能和选项。当他们连接到外部系统和服务(例如数据库、事件代理、云/Web 服务)时,他们还必须为这些服务保留越来越多的配置和凭据。

在运行时存储此信息的传统位置是全局变量和操作系统环境变量。两者都很烂。配置数据在逻辑上是“全局环境”或应用程序运行的上下文,但您不能轻易依赖全局或环境变量。

另一种传统机制,配置文件——无论是 XML、INI、.properties 还是其他——帮助在运行之间存储配置数据,但在程序代码或执行期间不做任何组织配置/选项的操作。

您可以通过将选项添加到应用程序类的属性中来稍微清理一下。这是传统的“下一步”。不幸的是,它可能需要很多前期“什么去哪里?” 思维。海事组织,超过它的价值。即使你做出正确的选择,它也不会持久。如果你有一个功能相当丰富的应用程序,随着时间的推移,选项和设置的数量将变得不堪重负。您将花费大量时间手动编写对象构造函数中的默认值和选项以及其他方法的参数/代码。试图在类之间完美地划分配置不仅会耗费精力,还会导致高度相互依赖的类。这么多干净的封装!

我经常把头撞在这堵墙上,尤其是在针对所有代码都具有“合理”或“智能”默认值和行为的代码时,它允许用户随时覆盖默认值,并且提供了一个简单的界面不需要了解应用程序类和组件的完整相互作用即可使用其服务。

解决方案:委托给一个配置对象我发现的最佳解决方案是将选项/配置数据封装到它自己的指定对象中。描述这种模式的一种奇特方式:对于配置、设置和选项数据,使用委托而不是继承组合

如何构建配置映射取决于您使用的语言。在许多语言中,构建自己的Config对象会给您一个漂亮的“外观”:

if opts.verbose:
    print "..."

我发现它比访问属性的更明确的“getter”opts.get("verbose")或“indexer”方式更具可读性。opts['verbose']但是你通常不必自己制作Config类,它基本上只是一个映射。

简单方法● 使用通用映射:例如在 Python 中 a dict,在 Perl 中 a %hash,在 Java 中 aDictionaryHashMap。更好的是,这些扩展是为配置数据设计的,或者特别适用于配置数据。例如,在 Python 中,我使用stufTreeDict来实现它们的简单点访问和其他不错的属性。在 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.

于 2012-07-28T19:03:18.870 回答
3

我建议将您的选项放入某种文件中(无论是 xml 还是 .properties),尤其是在值可以更改的情况下(例如数据库用户名等)。我还要说您可以按组件分解这些配置文件。就像您分解代码一样,需要数据库信息的组件可能不需要图像路径。所以你可以有一个用于数据库信息、图像路径信息等的文件。然后让你的组件加载他们需要的文件。

在特定于 java 的情况下,您可以将这些文件放在类路径中并让组件引用它们。我喜欢使用 .properties 文件,因为 java 有一个很好的类Properties来处理它们。

所以这里有一个图像提供者的小例子,它给你一个 BufferedImage,给定文件名。

图像属性

 icon.path=/path/to/my/icons
 background.path=/path/to/my/backgrounds

确保此文件位于您的类路径中。然后这是我的提供者类

 public class ImageProvider {
    private static final String PROP_FILE = "image.properties"; 
    private static final String ICON_PATH = "icon.path";

    private Properties properties; 

    public ImageProvider() {
      properties = new Properties();
      properties.load(getClass().getClassLoader().getResourceAsStream(PROP_FILE));
    }

    public BufferedImage getIcon(String icon) {
      return ImageIO.read(properties.getProperty(ICON_PATH) + icon);
    }
}
于 2012-07-28T17:02:06.300 回答
0

times when configuration was kept in static variables or when every component access them independently has ended long ago. since then mankind has invented IoC and DI. you take a DI library (e.g. spring), use it to load all the configuration (from files, jndi, environment, user, you name it) and inject it to every component that needs it. no manual resource loading, no static configuration - it's just evil

于 2012-07-29T15:37:01.513 回答