docs/archives/101-singleton-refactor/plan.md
经过深入排查,我们发现当前架构存在一个核心缺陷:服务实例在模块导入时被过早创建(Eager Instantiation),并作为单例(Singleton)在多个包之间导出和传递。
这导致了以下严重问题:
Dexie (IndexedDB) 的Web端服务。这些服务虽然未被最终使用,但占用了资源并造成了数据混乱的假象。@prompt-optimizer/ui 包不必要地导出了核心服务实例,使其职责不清,更像一个服务中转站而非纯UI库。本次重构的核心目标是实现服务的延迟初始化(Lazy Initialization)和依赖注入(Dependency Injection),确保只在需要时、在正确的环境中、创建唯一正确的服务实例。
core, ui)都不应再导出预先创建好的服务实例。core 只提供服务类和工厂函数,ui 只提供UI组件和Hooks,应用入口(App.vue)负责编排。本次重构已圆满完成。所有核心服务均已从单例模式迁移至工厂函数和依赖注入模式,实现了按需、按环境创建服务实例的目标。
目标:将所有服务的单例导出模式(export const service = new Service()) 改为工厂函数模式 (export function createService())。
步骤:
services/storage/factory.ts: 移除 storageProvider 单例导出。services/model/manager.ts: 移除 modelManager 单例导出,并使其工厂函数接收依赖。services/template/manager.ts: 移除 templateManager 单例导出,并使其工厂函数接收依赖。services/history/manager.ts: 移除 historyManager 单例导出,并使其工厂函数接收依赖。index.ts: 更新入口文件,确保只导出模块和工厂函数。期间发现的偏差及处理:
TemplateManager 的深层依赖:
TemplateManager 依赖另一个未被发现的单例 templateLanguageService。services/template/languageService.ts 进行了相同的重构,移除了单例并创建了 createTemplateLanguageService 工厂函数。相应地,createTemplateManager 现在接收 storageProvider 和 languageService 两个实例作为参数。index.ts 的导出清理:
index.ts 导出了属于应用层的 electron-proxy.ts 文件。index.ts,移除了这些不应由 core 包暴露的导出项,使 API 更纯净。目标:让 @prompt-optimizer/ui 回归其纯粹的UI库职责。
packages/ui/src/index.ts
@prompt-optimizer/core 重新导出的服务实例。UI包已回归纯UI库职责。目标:将所有初始化逻辑收敛到一个可复用的 composable 中。
packages/ui/src/composables/useAppInitializer.ts (新建)
create... 工厂函数和 Electron 代理类。services 和 isInitializing refs。onMounted 中,通过 isRunningInElectron() 判断环境:
storageProvider)。services ref 中。isInitializing 状态。App.vue) (已完成) ✅目标:让应用入口变得简洁,只负责消费初始化器返回的服务。
packages/web/src/App.vue & packages/extension/src/App.vue
useAppInitializer 返回的服务,实现了清晰的初始化流程。App.vue 下的所有UI子组件(如 ModelSelect, TemplateSelect 等),使其不再直接导入服务单例,而是通过 props 或 inject 接收服务实例,彻底完成了UI层的架构统一。Dexie 将只在Web环境下被创建一次。useAppInitializer -> App.vue -> Components,单向且清晰。这个计划将从根本上解决我们发现的架构问题,为项目未来的可维护性和可扩展性奠定坚实的基础。
本次重构成功地将核心服务从单例模式转换为了工厂函数模式,解决了环境隔离和状态不一致的根本问题。然而,在修复因此产生的大量测试失败的过程中,我们也总结出了一些宝贵的经验和需要进一步完善的设计决策:
ensureInitialized()Manager 实例后,必须手动调用 await manager.ensureInitialized() 来完成异步初始化。这虽然将实例的创建和初始化过程解耦,但也暴露了内部实现细节,增加了调用者的负担。createTemplateManager)本身成为一个异步函数,内部处理完所有初始化逻辑后,直接返回一个完全可用的实例 Promise<Manager>。这样调用者只需 await 一次,接口更简洁、封装性更好。TemplateManager 在初始化时若遇到存储错误,会静默地降级使用内置模板,而不是抛出错误。TemplateManager 在初始化遇到存储访问等关键错误时,必须向上抛出异常。由应用的顶层逻辑来捕获并决定如何处理(如向用户报错、进入安全模式等)。expect.objectContaining 等方式增强了测试的稳定性和可靠性。所有核心测试已通过。属性类型检查失败、响应式状态丢失和服务未初始化在内的一系列连锁问题。composables-refactor-plan.md 和 web-refactor-plan.md。核心对策是:1) 将返回多个 ref 的 Composable 重构为返回单个 reactive 对象,以解决属性传递问题。2) 在组件层级,通过 provide/inject 机制注入服务,减少了属性钻孔 (props drilling)。这次经历表明,底层架构的重大变更,必须伴随对上层应用影响的充分评估和细致的改造计划。此清单中的所有项目均已在最近的提交中完成。
文件: packages/core/src/services/storage/factory.ts
export const storageProvider = StorageFactory.createDefault();文件: packages/core/src/services/model/manager.ts
export const modelManager = ...export function createModelManager(storageProvider?: IStorageProvider): ModelManager
export function createModelManager(storageProvider: IStorageProvider): ModelManagerstorageProvider = storageProvider || StorageFactory.createDefault();文件: packages/core/src/services/template/manager.ts
export const templateManager = ...文件: packages/core/src/services/history/manager.ts
export const historyManager = ...文件: packages/core/src/services/data/manager.ts
export const dataManager = ...constructor() -> constructor(modelManager: IModelManager, templateManager: ITemplateManager, historyManager: IHistoryManager)createDataManager() -> createDataManager(modelManager: IModelManager, templateManager: ITemplateManager, historyManager: IHistoryManager)packages/ui/src/index.ts
export {
templateManager,
modelManager,
historyManager,
dataManager,
storageProvider,
createLLMService,
createPromptService
} from '@prompt-optimizer/core'
createDataManager 等其他必要的工厂函数。packages/ui/src/composables/useAppInitializer.ts (新建)
create... 工厂函数和 Electron 代理类。services 和 isInitializing refs。onMounted 中,通过 isRunningInElectron() 判断环境:
storageProvider)。services ref 中。isInitializing 状态。packages/web/src/App.vue & packages/extension/src/App.vue
modelManager, templateManager, historyManager 等服务单例的导入。import { modelManager, ... } from '@prompt-optimizer/ui'import { useAppInitializer } from '@prompt-optimizer/ui'const { services, isInitializing } = useAppInitializer();v-if="!isInitializing",并添加一个 v-else 的加载状态。services.value 作为 props 传递给需要的子组件,或在 composable 中使用 services.value.modelManager 等。onMounted 中手动的初始化逻辑。