7.5 传递式变量
Last updated
Last updated
另一种跨层的API重复形式是传递式变量(pass-through variable),这指的是通过一长串方法传递下来的变量。图7.2(a)显示了一个来自数据中心服务的例子。一个命令行参数描述了要用于安全通信的证书。这个信息只被一个低级别的方法m3
所需要,此方法调用一个库方法来打开一个套接字,但这个证书被传递到main
和m3
之间的所有方法中。cert
变量出现在每个中间方法的签名中。
传递式变量增加了复杂性,因为它迫使所有的中间方法都知道它们的存在,尽管这些方法不需要用到这些变量。此外,如果一个新的变量出现了(例如,一个系统最初构建时不支持证书,但你后来决定添加该支持),你可能不得不修改大量的接口和方法来通过所有的相关路径传递该变量。
消除传递式变量可能是一个挑战。一种方法是查看是否已经在最顶层和最底层方法之间共享了一个对象。在图 7.2 的数据中心服务示例中,可能有一个对象包含有关网络通信的其他信息,该对象对 main
和 m3
都可用。如果是这样,main
可以将证书信息存储在该对象中,因此无需通过所有的中间方法传递给 m3
(参见图 7.2(b))。但是,如果存在这样的对象,那么它本身就可能是一个传递式变量(否则m3
如何访问它呢?)。
另一种方法是将信息存储在一个全局变量中,如图7.2(c)所示。这就避免了在方法之间传递信息的需要,但全局变量几乎总是会造成其他问题。例如,全局变量使得在同一进程中不可能创建同一个系统的两个独立实例,因为对全局变量的访问会发生冲突。在生产中,你似乎不太可能需要多个实例,但它们在测试中往往是有用的。
我最常使用的解决方案是引入一个上下文对象,如图7.2(d)所示。 一个上下文存储了应用程序的所有全局状态(任何本来属于传递式变量或全局变量的东西)。大多数应用程序的全局状态都有多个变量,代表了诸如配置选项、共享子系统和性能计数器等内容。每个系统的实例有一个上下文对象。上下文允许系统的多个实例共存于一个进程中,每个实例都有自己的上下文。
不幸的是,上下文可能在很多地方都需要,所以它有可能成为一个传递式变量。为了减少必须知道它的方法的数量,可以在系统的大多数主要对象中保存对上下文的引用。在图7.2(d)的例子中,包含m3
的类将对上下文的引用作为实例变量保存在其对象中。创建新对象时,创建方法会从其对象中检索上下文引用并将其传递给新对象的构造函数。 通过这种方法,上下文在任何地方都是可用的,但它只在构造函数中作为一个显式参数出现。
上下文对象统一了对所有系统全局信息的处理,消除了对传递式变量的需求。如果需要添加一个新的变量,它可以被添加到上下文对象中;除了上下文的构造函数和析构函数外,没有任何现有的代码受到影响。上下文使得识别和管理系统的全局状态变得很容易,因为它全部存储在一个地方。上下文对于测试来说也很方便:测试代码可以通过修改上下文中的字段来改变应用程序的全局配置。如果系统使用传递式变量,要实现这样的变更就会困难得多。
上下文远非理想的解决方案。存储在上下文中的变量具有全局变量的大部分缺点;例如,为什么存在特定变量或在何处使用它可能并不明显。如果没有规范,上下文会变成一个巨大的数据摸彩袋,在整个系统中产生不明显的依赖。上下文还可能产生线程安全问题;避免问题的最佳办法是让上下文中的变量不可变。不幸的是,我还没有找到一个比上下文更好的解决方案。