模块系统中的单例工厂本质是依托模块缓存机制确保跨模块共享同一对象,其核心在于受控初始化而非防重复实例化;工厂函数解决延迟初始化与条件构造问题,比类单例更灵活、解耦且利于测试。

模块系统中的“单例工厂”模式,本质不是制造一个类的唯一实例,而是通过**模块加载机制+受控初始化逻辑**,确保跨模块访问的同一状态载体始终指向内存中同一个对象。它的价值不在于“防止多次 new”,而在于“让所有 import 都拿到同一个可变容器,并保证这个容器只被初始化一次”。
模块缓存才是真正的单例基础
Python、Node.js(CommonJS/ESM)、ArkTS 等现代模块系统都内置了模块缓存机制(如 Python 的 sys.modules、Node.js 的 require.cache)。只要路径一致,重复 import 返回的是已执行过的模块对象——它的顶层变量天然共享。
- 你不需要写
__new__或单例装饰器,直接在config.py中写settings = {"debug": True}即可 - 所有
from config import settings或import config拿到的都是同一个字典对象 - 修改
settings["debug"] = False,所有模块立刻感知,无需事件通知或重新导入
工厂部分解决的是“延迟初始化”和“条件构造”问题
纯模块变量适合静态配置,但遇到需要运行时参数、异步加载、依赖注入或环境判断的场景,就需要“工厂”介入:
- 比如数据库连接不能在模块顶层就创建(可能还没读取配置),而是导出一个
get_db_connection()函数 - 该函数内部检查是否已有连接实例,没有则按当前环境参数新建,有则复用
- 它把“单例保障”从类定义层,下沉到函数调用层,更灵活、更贴近业务上下文
它比传统类单例更适合模块化协作
在多团队并行开发中,类单例容易引发耦合和测试困难:A 团队写的 Logger.getInstance() 可能隐式依赖 B 团队未初始化的配置模块。而模块级单例工厂:
- 天然符合模块导入顺序语义,依赖关系清晰可见
- 单元测试时可直接 patch 模块变量或重置工厂内部缓存,无需 mock 构造函数
- 支持热重载友好:模块重载后,工厂可主动清空旧实例,下次调用重建
避免常见陷阱的关键细节
看似简单,但实际落地常踩坑:
-
不可变对象赋值会切断引用:写
state = {"a": 1}再赋值state = {"b": 2},其他模块看不到更新;必须用state.clear()、state.update(...)或操作其属性 -
导入路径不一致导致多个模块副本:
from mypkg.config import settings和import mypkg.config在某些打包工具下可能触发两次加载;统一用一种导入方式,或使用绝对路径别名 - 顶层代码执行时机不可控:模块中直接执行的初始化逻辑,可能早于配置加载;应把初始化逻辑收进工厂函数,由首次调用触发

















