# OOP vs Composables 架构对比分析

**日期**: 2026-01-31
**目的**: 探讨使用面向对象方式减少初始化顺序问题的可行性

---

## 1. 问题场景回顾

### 当前遇到的初始化问题

```typescript
// 问题1: 解构遗漏
const { previewUrl, isImageView, isAudioView } = useFilePreview()
// ❌ 忘记解构 updatePreviewUrl，导致后续 undefined

// 问题2: 函数定义顺序
const config = computed(() => useHelper())  // Line 362
const useHelper = () => { ... }             // Line 869
// ❌ 相差507行，导致初始化错误

// 问题3: 返回值过多
const {
  previewUrl, updatePreviewUrl, isImageView,
  isVideoView, isAudioView, isPdfFile,
  isHtmlFile, isMarkdownFile,
  getPreviewUrl, previewImage,
  // ... 还有15+个
} = useFilePreview()
// ❌ 难以维护，容易出错
```

---

## 2. 方案对比

### 方案A: Composables（当前方案）

#### 代码示例

```typescript
// composables/useFilePreview.ts
export function useFilePreview(options: UseFilePreviewOptions) {
  const previewUrl = ref('')
  const imageLoading = ref(false)

  const updatePreviewUrl = (url: string) => {
    previewUrl.value = url
  }

  const previewImage = async (path: string) => {
    imageLoading.value = true
    // ...
  }

  // 返回15+个值
  return {
    previewUrl,
    imageLoading,
    updatePreviewUrl,
    previewImage,
    isImageView,
    isVideoView,
    // ... 还有10+个
  }
}
```

#### 使用方式

```typescript
// index.vue
const {
  previewUrl,
  updatePreviewUrl,
  isImageView
} = useFilePreview({ filePath })

// 使用
updatePreviewUrl(url)
```

#### 优点
- ✅ 符合 Vue 3 Composition API 理念
- ✅ 函数式，灵活性高
- ✅ 可以选择性解构需要的值
- ✅ Tree-shaking 友好

#### 缺点
- ❌ 解构时容易遗漏函数
- ❌ 返回值过多时难以管理
- ❌ 初始化顺序依赖手动保证
- ❌ 状态分散，内聚性低

---

### 方案B: OOP + Class（提议方案）

#### 代码示例

```typescript
// services/FilePreviewService.ts
export class FilePreviewService {
  // ========== 状态 ==========
  private readonly _previewUrl = ref<string>('')
  private readonly _imageLoading = ref<boolean>(false)
  private readonly _currentImageDimensions = ref<string>('')

  // ========== 依赖注入 ==========
  constructor(
    private readonly filePath: Ref<string>,
    private readonly fileServer: FileServerService,
    private readonly options: FilePreviewOptions = {}
  ) {
    // 构造函数保证初始化顺序
    this.initialize()
  }

  // ========== 公共接口 ==========

  // Getter（访问器）
  get previewUrl(): string {
    return this._previewUrl.value
  }

  get imageLoading(): boolean {
    return this._imageLoading.value
  }

  // 方法
  updatePreviewUrl(url: string): void {
    this._previewUrl.value = url
  }

  async previewImage(path: string): Promise<void> {
    this._imageLoading.value = true
    try {
      const url = await this.fileServer.getPreviewUrl(path)
      this.updatePreviewUrl(url)
    } finally {
      this._imageLoading.value = false
    }
  }

  isImageFile(path: string): boolean {
    return FileTypes.isImage(path)
  }

  // ========== 私有方法 ==========

  private initialize(): void {
    // 初始化逻辑，保证顺序
    this.loadPreviewSettings()
  }

  private async loadPreviewSettings(): Promise<void> {
    // ...
  }
}

// 工厂函数（可选）
export function createFilePreviewService(
  filePath: Ref<string>,
  fileServer?: FileServerService
): FilePreviewService {
  return new FilePreviewService(
    filePath,
    fileServer ?? new FileServerService()
  )
}
```

#### 使用方式

```typescript
// index.vue
// 方式1: 直接实例化
const previewService = new FilePreviewService(
  filePath,
  new FileServerService()
)

// 方式2: 工厂函数
const previewService = createFilePreviewService(filePath)

// 使用
previewService.updatePreviewUrl(url)
const isLoading = previewService.imageLoading
```

#### 优点
- ✅ **构造函数保证初始化顺序**
- ✅ **状态和行为绑定（高内聚）**
- ✅ **类型安全，IDE 自动完成更好**
- ✅ **不会遗漏方法（都有实例.提示）**
- ✅ **依赖注入，易于测试**
- ✅ **私有方法封装性好**

#### 缺点
- ❌ 与 Vue 3 Composition API 理念不完全一致
- ❌ 需要手动管理实例生命周期
- ❌ 失去 composables 的部分灵活性
- ❌ 可能带来额外的内存开销

---

## 3. 混合方案（推荐）

### Composables + Service Layer

```typescript
// composables/useFilePreview.ts（轻量级）
import { FilePreviewService } from '@/services/FilePreviewService'

export function useFilePreview(options: UseFilePreviewOptions) {
  // 创建服务实例
  const service = new FilePreviewService(
    options.filePath,
    new FileServerService()
  )

  // 返回响应式接口
  return {
    // 响应式状态（直接返回 ref）
    previewUrl: service.previewUrlRef,
    imageLoading: service.imageLoadingRef,

    // 方法（绑定实例）
    updatePreviewUrl: (url: string) => service.updatePreviewUrl(url),
    previewImage: (path: string) => service.previewImage(path),

    // 服务实例（可选，用于高级用法）
    $service: service
  }
}

// services/FilePreviewService.ts（核心逻辑）
export class FilePreviewService {
  private readonly _previewUrl = ref<string>('')

  constructor(
    private readonly filePath: Ref<string>,
    private readonly fileServer: FileServerService
  ) {}

  get previewUrlRef(): Ref<string> {
    return this._previewUrl
  }

  updatePreviewUrl(url: string): void {
    this._previewUrl.value = url
  }
}
```

#### 使用方式

```typescript
// 简单使用（和之前一样）
const { previewUrl, updatePreviewUrl } = useFilePreview({ filePath })

// 高级使用（直接访问服务）
const { $service } = useFilePreview({ filePath })
$service.previewImage(path)  // 访问所有方法
```

#### 优势
- ✅ 保留 Composition API 的便利性
- ✅ 核心逻辑使用 OOP，保证初始化顺序
- ✅ 两种使用方式，灵活性高
- ✅ 渐进式重构，成本低

---

## 4. 实际应用示例

### 文件系统服务架构

```typescript
// ========== 服务层（核心逻辑） ==========

// services/FileSystemService.ts
export class FileSystemService {
  constructor(
    private readonly fileApi: FileApiService,
    private readonly previewService: FilePreviewService,
    private readonly zipService: ZipBrowserService
  ) {
    this.initializeServices()
  }

  private initializeServices(): void {
    // 保证服务初始化顺序
    this.previewService.initialize()
    this.zipService.initialize()
  }

  async loadDirectory(path: string): Promise<FileItem[]> {
    return await this.fileApi.listDirectory(path)
  }

  async previewFile(file: FileItem): Promise<void> {
    if (file.is_dir) return

    if (this.zipService.isBrowsingZip) {
      await this.zipService.previewZipFile(file.path)
    } else {
      await this.previewService.previewFile(file.path)
    }
  }
}

// services/FilePreviewService.ts
export class FilePreviewService {
  private readonly _previewUrl = ref<string>('')
  private readonly _fileContent = ref<string>('')

  constructor(
    private readonly fileApi: FileApiService,
    private readonly filePath: Ref<string>
  ) {}

  async previewFile(path: string): Promise<void> {
    const ext = FileTypes.getExtension(path)

    if (FileTypes.isImage(ext)) {
      await this.previewImage(path)
    } else if (FileTypes.isCode(ext)) {
      await this.loadCodeContent(path)
    }
  }

  private async previewImage(path: string): Promise<void> {
    const url = await this.fileApi.getImageUrl(path)
    this._previewUrl.value = url
  }
}

// services/ZipBrowserService.ts
export class ZipBrowserService {
  private readonly _isBrowsingZip = ref<boolean>(false)
  private readonly _currentZipPath = ref<string>('')

  constructor(
    private readonly fileApi: FileApiService,
    private readonly previewService: FilePreviewService
  ) {
    // 依赖注入，保证 previewService 已初始化
  }

  async enterZipMode(zipPath: string): Promise<void> {
    this._currentZipPath.value = zipPath
    this._isBrowsingZip.value = true
    await this.loadZipRoot()
  }

  async previewZipFile(filePath: string): Promise<void> {
    // 可以安全地调用 previewService
    await this.previewService.previewFile(filePath)
  }
}

// ========== Composables 层（轻量封装） ==========

// composables/useFileSystem.ts
export function useFileSystem() {
  // 创建服务实例（初始化顺序由构造函数保证）
  const fileApi = new FileApiService()
  const previewService = new FilePreviewService(fileApi, filePath)
  const zipService = new ZipBrowserService(fileApi, previewService)
  const fileSystemService = new FileSystemService(
    fileApi,
    previewService,
    zipService
  )

  // 返回响应式接口
  return {
    // 状态
    fileList: ref<FileItem[]>([]),
    fileLoading: ref(false),

    // 方法（委托给服务）
    loadDirectory: (path: string) => fileSystemService.loadDirectory(path),
    previewFile: (file: FileItem) => fileSystemService.previewFile(file),

    // 服务实例（可选）
    $services: {
      fileSystem: fileSystemService,
      preview: previewService,
      zip: zipService
    }
  }
}
```

### 使用示例

```typescript
// index.vue
const {
  fileList,
  fileLoading,
  loadDirectory,
  previewFile,
  $services  // 访问完整服务
} = useFileSystem()

// 简单使用
await loadDirectory(path)

// 高级使用（访问所有服务方法）
if ($services.zip.isBrowsingZip) {
  await $services.zip.navigateToZipDirectory(path)
}
```

---

## 5. 对比总结表

| 维度 | Composables | OOP + Class | 混合方案 |
|-----|-------------|-------------|---------|
| **初始化顺序保证** | ❌ 手动保证 | ✅ 构造函数保证 | ✅ 构造函数保证 |
| **内聚性** | ⚠️ 状态分散 | ✅ 高 | ✅ 高 |
| **类型安全** | ⚠️ 解构容易出错 | ✅ 严格 | ✅ 严格 |
| **IDE 支持** | ⚠️ 中等 | ✅ 优秀 | ✅ 优秀 |
| **代码复用** | ✅ 灵活 | ⚠️ 需要继承 | ✅ 灵活 |
| **测试性** | ⚠️ 需要模拟依赖 | ✅ 依赖注入 | ✅ 依赖注入 |
| **学习曲线** | ✅ 平缓 | ⚠️ 需要OOP经验 | ⚠️ 中等 |
| **重构成本** | ✅ 低 | ❌ 高 | ⚠️ 中等 |
| **与 Vue 3 兼容** | ✅ 完美 | ⚠️ 需要适配 | ✅ 良好 |
| **性能** | ✅ 轻量 | ⚠️ 可能有开销 | ✅ 轻量 |

---

## 6. 推荐方案

### 短期（1-2周）：保持 Composables + 改进规范

```typescript
// 添加分区注释，保证顺序
// ========== 1. 工具函数 ==========
const isEditableWithPreview = (filename: string): boolean => { ... }

// ========== 2. 状态变量 ==========
const fileList = ref<FileItem[]>([])

// ========== 3. Composables ==========
const { previewUrl, updatePreviewUrl } = useFilePreview({ filePath })

// ========== 4. Computed ==========
const config = computed(() => ({
  canPreview: isEditableWithPreview(filename)  // ✅ 函数已定义
}))
```

### 中期（1-2月）：混合方案

```typescript
// 新功能使用 OOP 服务
// 老功能保持 Composables
// 逐步迁移
```

### 长期（3-6月）：全面 OOP 架构

```typescript
// 所有核心逻辑使用服务类
// Composables 仅作为轻量级适配器
```

---

## 7. 实施建议

### 如果采用混合方案，分步骤：

#### 步骤1：创建服务层（不影响现有代码）

```typescript
// services/FilePreviewService.ts
export class FilePreviewService {
  // 新代码使用 OOP
}
```

#### 步骤2：创建适配器 Composable

```typescript
// composables/useFilePreview.ts
export function useFilePreview(options) {
  const service = new FilePreviewService(...)

  return {
    // 响应式接口
    previewUrl: service.previewUrlRef,
    // 方法
    updatePreviewUrl: (url) => service.updatePreviewUrl(url)
  }
}
```

#### 步骤3：逐步迁移现有代码

```typescript
// 老代码
const { previewUrl, updatePreviewUrl } = useFilePreview({ filePath })

// 新代码（可以直接使用服务）
const service = new FilePreviewService(filePath)
service.updatePreviewUrl(url)
```

---

## 8. 结论

### OOP 方案能解决当前问题吗？

**✅ 能解决：**
1. 初始化顺序问题（构造函数保证）
2. 解构遗漏问题（实例.调用）
3. 返回值过多问题（清晰的接口）
4. 内聚性问题（状态+行为绑定）

### 但需要权衡：

**❌ 潜在问题：**
1. 与 Vue 3 理念不完全一致
2. 重构成本较高
3. 团队学习曲线

### 最佳方案：

**🎯 推荐：混合方案**
- 核心逻辑使用 OOP 服务类
- Composables 作为轻量适配器
- 渐进式迁移，降低风险

---

**生成时间**: 2026-01-31
**下一步**: 是否创建一个示例服务类验证可行性？
