# Singleton

## Description

Singleton 模块用于提供单例（singleton）相关的基础能力，主要承载惰性初始化（lazy initialization）、结果缓存以及具名共享实例的组织语义。

它关注的不是“如何制造全局变量”，而是“当某个值确实只应初始化一次并被稳定复用时，应如何用清楚、可维护的方式表达这种共享关系”。因此，这个模块更适合用来建模实例生命周期与访问边界，而不是无差别地为任何对象增加全局缓存入口。

## For Understanding

理解 Singleton 模块时，重点不在“单例”这个词本身，而在“共享值的生命周期由谁负责、初始化在什么时候发生、调用方如何稳定访问它”。这个模块解决的是共享实例的建模问题：某个值是否应在首次读取时才创建、是否应在后续始终复用、是否需要通过名称组织多个共享项。

因此，它适合放在那些需要明确所有权和初始化时机的边界中，例如基础配置、昂贵对象构造结果、应用级共享资源或一组可按名称访问的依赖项。它不适合表达的，则是带有复杂销毁规则、作用域切换规则或线程级隔离要求的资源管理问题；那类问题通常需要更完整的生命周期模型，不能仅靠“只创建一次”来概括。

还需要注意的是，单例只是一种共享策略，不应被误解为默认设计。只有当值的复用边界、初始化副作用和缓存语义都足够清楚时，它才适合进入这个模块。否则，过早把普通依赖硬塞进单例模型，只会把状态管理问题隐藏起来，而不会真正减少复杂度。

## For Using

当你需要把某个值设计为“首次访问时初始化，之后持续复用”的共享实例时，可以使用这个模块。它适合那些初始化成本较高、创建时机需要延后、并且复用边界明确的场景。

从使用角度看，这个模块大致包含两类能力。一类是惰性工厂能力，用来把一次性初始化逻辑包装成可复用入口，使值只在真正需要时才被创建。另一类是具名单例集合能力，用来把多个共享实例组织成一个稳定的命名集合，便于调用方按名称读取相应的缓存结果。

当调用方需要显式放弃已有缓存并在下一次访问时重新初始化时，可以使用单例项或单例集合上的 `reset()` 方法。这个能力尤其适合测试场景中控制缓存生命周期，或少数确实需要重新装载配置、连接参数等共享值的边界位置；但如果系统已经进入更复杂的生命周期管理问题，仍应优先考虑更明确的作用域或资源模型，而不是把 `reset()` 当作通用状态机。

更合理的使用方式，是把单例能力放在应用或模块边界附近，并让初始化逻辑保持尽量确定。特别是在初始化会接触 I/O、环境变量、全局对象或其它外部资源时，应先明确这些副作用是否真的适合缓存。测试场景中也应明确控制缓存状态的生命周期，避免不同用例之间因为共享状态而互相影响。

## For Contributing

贡献 Singleton 模块时，应优先判断新增能力解决的是不是稳定的共享生命周期问题，而不是某个局部实现里一时方便的缓存技巧。这个模块的公共语义应围绕“值只初始化一次并被复用”以及“多个共享项如何被命名和访问”展开，而不是替调用方偷偷管理越来越复杂的状态机。

在继续扩展时，应守住几个边界。不要把作用域管理、资源销毁、并发隔离或依赖注入容器的完整语义混入 Singleton 模块；这些问题虽然与共享实例有关，但通常比单例语义更大。也不要为了省掉几次参数传递，就把本应显式注入的依赖升级为全局单例。只有当共享关系本身就是模块想表达的稳定模型时，新增能力才值得公开。

### JSDoc 注释格式要求

- 每个公开导出的目标（类型、函数、变量、类等）都应包含 JSDoc 注释，让人在不跳转实现的情况下就能理解用途。
- JSDoc 注释第一行应为清晰且简洁的描述，该描述优先使用中文（英文也可以）。
- 如果描述后还有其他内容，应在描述后加一个空行。
- 如果有示例，应使用 `@example` 标签，后接三重反引号代码块（不带语言标识）。
- 如果有示例，应包含多个场景，展示不同用法，尤其要覆盖常见组合方式或边界输入。
- 如果有示例，应使用注释格式说明每个场景：`// Expect: <result>`。
- 如果有示例，应将结果赋值给 `example1`、`example2` 之类的变量，以保持示例易读。
- 如果有示例，`// Expect: <result>` 应该位于 `example1`、`example2` 之前，以保持示例的逻辑清晰。
- 如果有示例，应优先使用确定性示例；避免断言精确的随机输出。
- 如果函数返回结构化字符串，应展示其预期格式特征。
- 如果有参考资料，应将 `@see` 放在 `@example` 代码块之后，并用一个空行分隔。

### 实现规范要求

- 不同程序元素之间使用一个空行分隔，保持结构清楚。这里的程序元素，通常指函数、类型、常量，以及直接服务于它们的辅助元素。
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体，不要在它们之间添加空行。
- 程序元素的辅助元素应该放置在该程序元素的上方，以保持阅读时的逻辑顺序。
- 若辅助元素被多个程序元素共享，则应将其视为独立的程序元素，放在这些程序元素中第一个相关目标的上方，并与后续程序元素之间保留一个空行。
- 辅助元素也应该像其它程序元素一样，保持清晰的命名和适当的注释，以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
- 辅助元素的命名必须以前缀 `internal` 开头（或 `Internal`，大小写不敏感）。
- 辅助元素永远不要公开导出。
- 被模块内多个不同文件中的程序元素共享的辅助元素，应该放在一个单独的文件中，例如 `./src/singleton/internal.ts`。
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时，才应将其视为子模块。
- 子模块包含多个文件时，应该为其单独创建子文件夹，并为其创建单独的 Barrel 文件；父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
- 子模块不需要有自己的 `README.md`。
- 子模块可以有自己的 `internal.ts` 文件，多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中，单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
- 对模块依赖关系的要求（通常是不循环依赖或不反向依赖）与对 DRY 的要求可能产生冲突。此时，若复用的代码数量不大，可以适当牺牲 DRY，复制粘贴并保留必要的注释说明；若复用的代码数量较大，则可以将其抽象到新的文件或子模块中，如 `common.ts`，并在需要的地方导入使用。

- 与单例相关的实现应优先围绕初始化时机、复用边界与具名共享语义组织，避免把销毁策略、作用域切换或依赖注入容器语义混入模块边界。

### 导出策略要求

- 保持内部辅助项和内部符号为私有，不要让外部接入依赖临时性的内部结构。
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
- Barrel 文件应命名为 `index.ts`，放在模块目录根部，并且所有公共 API 都应从该文件导出。
- 新增公共能力时，应优先检查它是否表达稳定、清楚且值得长期维护的单例语义，而不是某段实现细节的便捷暴露；仅在确认需要长期对外承诺时再加入 Barrel 导出。

### 测试要求

- 若程序元素是函数，则只为该函数编写一个测试，如果该函数需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，则至少要为该类的每一个方法编写一个测试，如果该方法需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，除了为该类的每一个方法编写至少一个测试之外，还可以为该类编写任意多个测试，以覆盖该类的不同使用场景或边界情况。
- 若编写测试时需要用到辅助元素（Mock 或 Spy 等），可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单，则可以直接放在每一个测试内部，优先保证每个测试的独立性，而不是追求极致 DRY；若辅助元素较为复杂或需要在多个测试中复用，则可以放在测试文件顶部，供该测试文件中的所有测试使用。
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
- 若该模块不需要测试，必须在说明文件中明确说明该模块不需要测试，并说明理由。一般来说，只有在该模块没有可执行的公共函数、只承载类型层表达，或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时，才适合这样处理。
- 模块的单元测试文件目录是 `./tests/unit/singleton`，若模块包含子模块，则子模块的单元测试文件目录为 `./tests/unit/singleton/<sub-module-name>`。
- 对单例相关能力，应优先覆盖首次初始化、重复访问复用、不同名称隔离、错误分支以及测试间状态控制等场景。
