知识卡片

注册表接口用静态方法,但数据不必是静态域

结构图卡

内容

注册表是一个”众所周知”的全局对象,让代码在没有合适的关联路径可循时(比如只知道客户ID、却没有指向客户对象的引用)也能找到需要的公共对象或服务。设计注册表要把接口和实现分开考虑——作者倾向于接口用静态方法(这样应用里任何地方都能方便地调用到),但特别提醒:方法是静态的,不代表背后的数据也必须存在静态域里,除了存常量,作者几乎不用静态域。决定数据该怎么存之前,先要看清数据的作用域:有的数据贯穿整个进程,有的只在单个线程内全局,有的只在一次会话内全局——不同作用域需要不同的存储实现,但对外暴露的接口应该保持一致,调用方不需要关心某个静态方法背后返回的到底是进程级数据还是线程级数据。进程作用域的数据,常见做法是用单子模式(内部用静态域存唯一实例,但对外只暴露隐藏这个实例的静态方法,而不是直接暴露实例本身);但单子在多线程环境下管理”可修改数据”很容易出乱子(多线程无法预知顺序地读写同一对象),除非数据本身是只读常量(比如美国州名列表这种进程启动时加载、运行期基本不变的数据),否则不建议用单子存可变数据。线程作用域的数据(如数据库连接)通常借助线程局部存储或者”以线程为键的字典”来实现,但对外接口写法(如Registry.getDbConnection())和进程作用域时应完全一致;会话作用域的数据同样可以用字典技术——每个会话开始时把会话ID记进线程作用域的注册表,后续按这个会话ID去查一个以会话为键的映射结构。

结构图

flowchart TB
  A["注册表:统一的静态方法接口"]
  A --> B["进程作用域<br/>单子(可变数据慎用/只读常量可用)"]
  A --> C["线程作用域<br/>线程局部存储或以线程为键的字典"]
  A --> D["会话作用域<br/>以会话ID为键的映射<br/>会话ID存在线程作用域中"]

参考来源

- 位置:《企业应用架构模式》第二部分"模式"之"第18章 基本模式"之"18.5.1 运行机制"(源文件:_epub-src/OEBPS/Text/000220.html) - 结论依据:原文说明"对于注册表而言,我倾向于在接口中使用静态方法……但是,方法的静态性并不意味着你的数据也必须保存在静态域中……不同作用域要求不同的实现,但它们的接口是一致的……单子在单线程应用程序中得到了广泛的使用,但是在多线程应用程序中可能会成为麻烦的根源……我不建议你在多线程环境下对可修改的数据使用单子模式……线程作用域的数据是一种常见的注册表数据……字典查询技术也可用于会话作用域的数据",直接支撑本卡结构图。 - 原始内容:应用程序员并不需要知道调用某个静态方法后返回的是进程作用域还是线程作用域的数据。