Back to Prompt Optimizer

导入导出接口设计重构

docs/architecture/import-export-interface-design.md

2.10.28.5 KB
Original Source

导入导出接口设计重构

📋 重构背景

用户提出了一个非常重要的架构观点:"当前由DataManager来实现import和export的具体实现不合理。应该抽象一个接口定义,有导入导出方法。让各个service类去继承,如 IModelManager、IPreferenceService等。要求他们必须实现这个接口。DataManager只负责总体协调,具体实现由各个类负责。"

🎯 问题分析

当前架构的问题

  1. 职责不清 - DataManager既要协调又要了解每个服务的具体实现细节
  2. 耦合度高 - DataManager需要知道如何调用每个服务的具体方法
  3. 扩展性差 - 新增服务需要修改DataManager的实现
  4. 违反单一职责原则 - DataManager承担了太多责任

目标架构

  1. 职责分离 - DataManager只负责协调,各服务负责自己的导入导出
  2. 接口统一 - 所有服务实现相同的导入导出接口
  3. 扩展性好 - 新增服务只需实现接口,无需修改DataManager
  4. 遵循开闭原则 - 对扩展开放,对修改关闭

🔧 解决方案

1. 定义统一的导入导出接口

typescript
/**
 * 可导入导出的服务接口
 * 所有需要参与数据导入导出的服务都应该实现此接口
 */
export interface IImportExportable {
  /**
   * 导出服务的所有数据
   * @returns 服务数据的JSON表示
   */
  exportData(): Promise<any>;

  /**
   * 导入数据到服务
   * @param data 要导入的数据
   * @returns 导入结果
   */
  importData(data: any): Promise<ImportExportResult>;

  /**
   * 获取服务的数据类型标识
   * 用于在导入导出JSON中标识数据类型
   */
  getDataType(): string;

  /**
   * 验证数据格式是否正确
   * @param data 要验证的数据
   * @returns 是否为有效格式
   */
  validateData(data: any): boolean;
}

2. 更新服务接口继承关系

typescript
// 所有需要导入导出的服务都继承IImportExportable
export interface IModelManager extends IImportExportable { /* ... */ }
export interface IPreferenceService extends IImportExportable { /* ... */ }
export interface ITemplateManager extends IImportExportable { /* ... */ }
export interface IHistoryManager extends IImportExportable { /* ... */ }

3. 实现简洁的DataCoordinator(简化后)

typescript
export class DataCoordinator implements IDataManager {
  private readonly services: IImportExportable[];

  // 直接通过构造函数注入所有服务,简单直接
  constructor(services: IImportExportable[]) {
    this.services = services;
  }

  /**
   * 导出所有数据 - 只负责协调
   */
  async exportAllData(): Promise<ExportData> {
    const data: Record<string, any> = {};

    // 并行导出所有服务的数据
    const exportPromises = this.services.map(async (service) => {
      const dataType = service.getDataType();
      const serviceData = await service.exportData();
      data[dataType] = serviceData;
    });

    await Promise.all(exportPromises);

    return { version: 1, timestamp: Date.now(), data };
  }

  /**
   * 导入所有数据 - 只负责协调
   */
  async importAllData(exportData: ExportData): Promise<ImportExportResult> {
    // 并行导入所有服务的数据
    const importPromises = Object.entries(exportData.data).map(async ([dataType, serviceData]) => {
      const service = this.services.find(s => s.getDataType() === dataType);
      if (service) {
        return await service.importData(serviceData);
      }
    });

    const results = await Promise.all(importPromises);
    // 汇总结果...
  }
}

// 使用示例:简单的工厂函数
export function createDataCoordinator(services: IImportExportable[]): DataCoordinator {
  return new DataCoordinator(services);
}

📊 架构对比

修改前:DataManager承担所有职责

typescript
// ❌ DataManager需要了解每个服务的具体实现
class DataManager {
  async exportAllData() {
    const userSettings = await this.preferenceService.getAll();
    const models = await this.modelManager.getAllModels();
    const templates = await this.templateManager.listTemplates();
    const history = await this.historyManager.getAllRecords();
    // DataManager需要知道每个服务的具体方法名和返回格式
  }
}

修改后:简洁的协调者模式

typescript
// ✅ DataCoordinator只负责协调,不关心具体实现
class DataCoordinator {
  constructor(services: IImportExportable[]) {
    this.services = services; // 简单的依赖注入
  }

  async exportAllData() {
    // 统一调用每个服务的exportData()方法
    const exportPromises = this.services.map(async (service) => {
      const dataType = service.getDataType();
      data[dataType] = await service.exportData();
    });
  }
}

// 使用时直接传入所有服务
const coordinator = new DataCoordinator([
  modelManager,
  preferenceService,
  templateManager,
  historyManager
]);

🎯 实现细节

各服务的实现示例

ModelManager实现

typescript
export class ModelManager implements IModelManager {
  async exportData(): Promise<ModelConfig[]> {
    return await this.getAllModels();
  }

  async importData(data: any): Promise<ImportExportResult> {
    if (!this.validateData(data)) {
      return { success: false, message: 'Invalid model data format' };
    }
    // 具体导入逻辑...
  }

  getDataType(): string {
    return 'models';
  }

  validateData(data: any): boolean {
    return Array.isArray(data) && data.every(/* 验证逻辑 */);
  }
}

PreferenceService实现

typescript
export class PreferenceService implements IPreferenceService {
  async exportData(): Promise<Record<string, string>> {
    return await this.getAll();
  }

  async importData(data: any): Promise<ImportExportResult> {
    if (!this.validateData(data)) {
      return { success: false, message: 'Invalid preference data format' };
    }
    // 具体导入逻辑...
  }

  getDataType(): string {
    return 'userSettings';
  }

  validateData(data: any): boolean {
    return typeof data === 'object' && /* 验证逻辑 */;
  }
}

🚀 优势总结

1. 职责清晰

  • DataCoordinator: 只负责协调各服务的导入导出
  • 各服务: 只负责自己数据的导入导出实现
  • 接口: 定义统一的行为规范

2. 扩展性强

  • 新增服务只需实现IImportExportable接口
  • 无需修改DataCoordinator的代码
  • 支持动态注册和注销服务

3. 可测试性好

  • 每个服务的导入导出逻辑可以独立测试
  • DataCoordinator的协调逻辑可以用mock服务测试
  • 接口定义明确,便于编写单元测试

4. 维护性高

  • 各服务的导入导出逻辑内聚在服务内部
  • 修改某个服务的导入导出逻辑不影响其他部分
  • 代码结构清晰,便于理解和维护

📝 迁移计划

已完成

  • 定义IImportExportable接口
  • 更新所有服务接口继承关系
  • 实现ModelManager的导入导出接口
  • 实现PreferenceService的导入导出接口
  • 实现TemplateManager的导入导出接口
  • 创建DataCoordinator协调者类

待完成

  • 实现HistoryManager的导入导出接口
  • 更新应用初始化代码使用DataCoordinator
  • 更新所有相关测试
  • 废弃旧的DataManager类

⚠️ 重要修正:接口兼容性

破坏性更新问题

在重构过程中,我们差点引入了破坏性更新:

typescript
// ❌ 原来的接口(破坏性更新)
async exportAllData(): Promise<ExportData>;
async importAllData(data: ExportData): Promise<ImportExportResult>;

// ✅ 修正后的接口(保持兼容)
async exportAllData(): Promise<string>;
async importAllData(dataString: string): Promise<ImportExportResult>;

兼容性原则

  1. 保持现有接口签名 - 不改变方法参数和返回类型
  2. 内部重构,外部不变 - 内部可以使用新的数据结构,但对外接口保持一致
  3. 渐进式升级 - 如需变更,先标记为deprecated,再逐步迁移

🎉 总结

这次重构体现了优秀的架构设计原则:

  1. 单一职责原则 - 每个类只负责一个职责
  2. 开闭原则 - 对扩展开放,对修改关闭
  3. 依赖倒置原则 - 依赖抽象而不是具体实现
  4. 接口隔离原则 - 接口设计精简且职责明确
  5. 向后兼容原则 - 保护现有调用代码,避免破坏性更新

用户的建议非常准确,不仅指出了架构问题,还及时发现了兼容性问题,让系统更加稳定和可维护。