HarmonyOS NEXT 统一 ToolManager 架构:插件化工具系统的设计与实现

前言

在 HarmonyExplorer 项目中,工具箱模块集成了文件压缩、格式转换、哈希计算等多种实用工具。随着工具数量增长,如何避免代码臃肿、实现工具的动态扩展成为架构设计的核心挑战。本文将详细讲解基于插件化理念的统一 ToolManager 架构设计,实现新增工具零侵入式扩展。参考 ArkTS 接口定义规范 了解接口设计要点。

一、ToolManager 架构设计理念

1.1 传统工具管理的痛点

在未引入 ToolManager 之前,工具箱页面通过 if-else 或 switch-case 硬编码管理工具调用。这种方式存在以下问题:

问题 影响 严重程度
新增工具需修改核心代码 违反开闭原则
工具间无法统一管理 维护成本高
工具历史记录分散 数据不一致
工具间无法共享数据 代码重复

1.2 插件化设计目标

ToolManager 的设计目标是构建一个 高内聚、低耦合 的工具管理系统:

  • 零侵入扩展:新增工具只需实现接口并注册,无需修改已有代码
  • 统一管理:所有工具的发现、加载、执行、历史记录统一处理
  • 分类组织:工具按类别管理,支持动态分组展示
  • 生命周期管理:工具的初始化、执行、销毁全程可控

插件化架构的核心价值在于将变化隔离,让系统在不修改稳定核心的前提下灵活扩展新能力。

二、插件化接口设计 ITool

2.1 ITool 接口定义

所有工具必须实现 ITool 接口,该接口定义了工具的生命周期方法和元数据。接口设计是整个架构的基石。

export enum ToolCategory {
  FILE = 'file',
  IMAGE = 'image',
  MEDIA = 'media',
  SECURITY = 'security',
  UTIL = 'util'
}

export interface ToolResult {
  success: boolean;
  data: string;
  message: string;
}

export interface ToolMetadata {
  id: string;
  name: string;
  description: string;
  category: ToolCategory;
  icon: Resource;
  isAvailable: boolean;
}

export interface ITool {
  getMetadata(): ToolMetadata;
  execute(input: string): Promise<ToolResult>;
  onActivate(): void;
  onDeactivate(): void;
}

2.2 抽象基类实现

为了减少重复代码,提供 AbstractTool 抽象基类,子类只需关注核心执行逻辑:

export abstract class AbstractTool implements ITool {
  protected metadata: ToolMetadata;

  constructor(metadata: ToolMetadata) {
    this.metadata = metadata;
  }

  getMetadata(): ToolMetadata {
    return this.metadata;
  }

  abstract execute(input: string): Promise<ToolResult>;

  onActivate(): void {
    LogUtil.info('工具激活: ' + this.metadata.name);
  }

  onDeactivate(): void {
    LogUtil.info('工具停用: ' + this.metadata.name);
  }
}

三、工具注册机制

3.1 注册器设计

ToolManager 内部维护一个工具注册表,支持按 ID 和类别检索。注册采用 Map 结构保证 O(1) 查找效率。

export class ToolManager {
  private static tools: Map<string, ITool> = new Map();
  private static categoryIndex: Map<ToolCategory, Array<string>> = new Map();

  static register(tool: ITool): void {
    const metadata: ToolMetadata = tool.getMetadata();
    this.tools.set(metadata.id, tool);
    this.addToCategoryIndex(metadata.category, metadata.id);
    LogUtil.info('工具注册成功: ' + metadata.name);
  }

  static unregister(toolId: string): void {
    const tool: ITool | undefined = this.tools.get(toolId);
    if (tool !== undefined) {
      const metadata: ToolMetadata = tool.getMetadata();
      this.removeFromCategoryIndex(metadata.category, toolId);
      tool.onDeactivate();
      this.tools.delete(toolId);
    }
  }

  private static addToCategoryIndex(category: ToolCategory, toolId: string): void {
    let ids: Array<string> | undefined = this.categoryIndex.get(category);
    if (ids === undefined) {
      ids = [];
      this.categoryIndex.set(category, ids);
    }
    ids.push(toolId);
  }

  private static removeFromCategoryIndex(category: ToolCategory, toolId: string): void {
    const ids: Array<string> | undefined = this.categoryIndex.get(category);
    if (ids !== undefined) {
      const index: number = ids.indexOf(toolId);
      if (index >= 0) {
        ids.splice(index, 1);
      }
    }
  }
}

3.2 工具发现与加载

工具注册在应用初始化时自动完成。通过 ToolRegistry 集中管理所有工具的注册调用:

export class ToolRegistry {
  static initAllTools(): void {
    ToolManager.register(new FileCompressTool());
    ToolManager.register(new FileHashTool());
    ToolManager.register(new ImageConvertTool());
    ToolManager.register(new AudioConvertTool());
    ToolManager.register(new Base64Tool());
    LogUtil.info('所有工具注册完成');
  }
}

四、具体工具实现示例

4.1 文件压缩工具

以下展示一个完整的工具实现,继承 AbstractTool 并实现 execute 方法:

export class FileCompressTool extends AbstractTool {
  constructor() {
    super({
      id: 'tool_file_compress',
      name: '文件压缩',
      description: '支持 ZIP 格式文件压缩',
      category: ToolCategory.FILE,
      icon: $r('app.media.ic_tool_compress'),
      isAvailable: true
    });
  }

  async execute(input: string): Promise<ToolResult> {
    try {
      const targetPath: string = input + '.zip';
      const success: boolean = await ZipManager.compressFiles(input, targetPath);
      return {
        success: success,
        data: targetPath,
        message: success ? '压缩成功' : '压缩失败'
      };
    } catch (error) {
      return {
        success: false,
        data: '',
        message: '压缩异常: ' + error.message
      };
    }
  }
}

4.2 文件哈希工具

export class FileHashTool extends AbstractTool {
  constructor() {
    super({
      id: 'tool_file_hash',
      name: '文件哈希',
      description: '计算文件 MD5/SHA256 值',
      category: ToolCategory.SECURITY,
      icon: $r('app.media.ic_tool_hash'),
      isAvailable: true
    });
  }

  async execute(input: string): Promise<ToolResult> {
    const hashValue: string = await HashUtil.calculateFileHash(input, 'SHA-256');
    return {
      success: hashValue.length > 0,
      data: hashValue,
      message: '哈希计算完成'
    };
  }
}

在这里插入图片描述

图1:ToolManager 插件化架构图,展示接口层、注册层和工具实现层的关系

五、ToolHistory 历史记录

5.1 历史记录模型

每次工具执行后自动记录历史,方便用户查看和复用。ToolHistory 数据模型如下:

export interface ToolHistory {
  id: string;
  toolName: string;
  content: string;
  createTime: number;
}

5.2 历史记录管理

import dataPreferences from '@ohos.data.preferences';

export class ToolHistoryRepository {
  private static preference: dataPreferences.Preferences | null = null;
  private static readonly MAX_HISTORY: number = 100;

  static async init(context: Context): Promise<void> {
    this.preference = await dataPreferences.getPreferences(context, 'tool_history');
  }

  static async addHistory(history: ToolHistory): Promise<void> {
    if (this.preference === null) { return; }
    const key: string = 'history_' + history.id;
    await this.preference.put(key, JSON.stringify(history));
    await this.preference.flush();
  }

  static async getHistoryList(): Promise<Array<ToolHistory>> {
    if (this.preference === null) { return []; }
    const all: Record<string, object> = await this.preference.getAll();
    const list: Array<ToolHistory> = [];
    const keys: Array<string> = Object.keys(all);
    for (const key of keys) {
      if (key.startsWith('history_')) {
        const history: ToolHistory = JSON.parse(String(all[key]));
        list.push(history);
      }
    }
    list.sort((a: ToolHistory, b: ToolHistory) => b.createTime - a.createTime);
    return list;
  }
}

六、工具分类管理

6.1 分类索引查询

ToolManager 提供按类别查询工具的能力,Toolbox 页面据此进行分组展示。HarmonyExplorer 中预定义的工具分类如下:

分类枚举 分类名称 典型工具示例
FILE 文件工具 文件压缩、文件哈希
IMAGE 图片工具 图片格式转换、图片压缩
MEDIA 媒体工具 音频转换、视频提取
SECURITY 安全工具 文件加密、哈希校验
UTIL 实用工具 二维码生成、Base64 编码
export class ToolManager {
  static getToolsByCategory(category: ToolCategory): Array<ITool> {
    const ids: Array<string> | undefined = this.categoryIndex.get(category);
    const result: Array<ITool> = [];
    if (ids !== undefined) {
      for (const id of ids) {
        const tool: ITool | undefined = this.tools.get(id);
        if (tool !== undefined && tool.getMetadata().isAvailable) {
          result.push(tool);
        }
      }
    }
    return result;
  }

  static getAllCategories(): Array<ToolCategory> {
    return Array.from(this.categoryIndex.keys());
  }

  static async executeTool(toolId: string, input: string): Promise<ToolResult> {
    const tool: ITool | undefined = this.tools.get(toolId);
    if (tool === undefined) {
      return { success: false, data: '', message: '工具不存在' };
    }
    tool.onActivate();
    const result: ToolResult = await tool.execute(input);
    const metadata: ToolMetadata = tool.getMetadata();
    await ToolHistoryRepository.addHistory({
      id: Date.now().toString(), toolName: metadata.name,
      content: result.data, createTime: Date.now()
    });
    tool.onDeactivate();
    return result;
  }
}

七、ToolCard 组件适配

7.1 组件设计

ToolCard 是工具箱页面的展示组件,直接消费 ToolMetadata 渲染工具卡片。参考 ArkUI 组件开发

7.2 ToolCard 实现

@Component
export struct ToolCard {
  @Prop metadata: ToolMetadata;
  onToolClick: (toolId: string) => void = () => {};

  build(): void {
    Column() {
      Image(this.metadata.icon).width(40).height(40).margin({ bottom: 8 })
      Text(this.metadata.name).fontSize(13).fontColor($r('app.color.text_primary')).maxLines(1)
      Text(this.metadata.description).fontSize(11)
        .fontColor($r('app.color.text_secondary')).maxLines(2).margin({ top: 2 })
    }
    .width('100%').padding(12).borderRadius(12)
    .backgroundColor($r('app.color.bg_card')).alignItems(HorizontalAlign.Center)
    .opacity(this.metadata.isAvailable ? 1.0 : 0.4)
    .onClick(() => {
      if (this.metadata.isAvailable) { this.onToolClick(this.metadata.id); }
    })
  }
}

八、工具间数据传递

8.1 数据传递机制

某些工具的输出可以作为另一个工具的输入,例如哈希计算结果可以传递给 Base64 编码工具。ToolManager 提供 ToolContext 管理工具间数据流:

export class ToolContext {
  private static dataMap: Map<string, string> = new Map();

  static setData(key: string, value: string): void {
    this.dataMap.set(key, value);
  }

  static getData(key: string): string {
    return this.dataMap.get(key) ?? '';
  }

  static clearData(key: string): void {
    this.dataMap.delete(key);
  }

  static clearAll(): void {
    this.dataMap.clear();
  }
}

工具间数据传递采用键值对存储模式,解耦了工具之间的直接依赖,任何工具都可以生产或消费数据。

九、ToolManager 与 KitManager 协作

9.1 职责边界

ToolManager 管理工具的注册与执行流程,KitManager 管理 HarmonyOS Kit 的能力封装。两者协作关系如下:

  1. 工具执行时通过 ToolManager 调度
  2. 工具内部调用 KitManager 获取系统能力
  3. KitManager 封装 File Kit、Image Kit 等底层 API
  4. ToolManager 记录执行历史,KitManager 不感知业务逻辑
维度 ToolManager KitManager
职责 工具生命周期管理 系统能力封装
依赖方向 调用 KitManager 不依赖 ToolManager
扩展方式 注册新 ITool 封装新 Kit
数据管理 ToolHistory 无状态

9.2 协作示例

export class ImageConvertTool extends AbstractTool {
  constructor() {
    super({
      id: 'tool_image_convert',
      name: '图片格式转换',
      description: '支持 PNG/JPEG/WebP 互转',
      category: ToolCategory.IMAGE,
      icon: $r('app.media.ic_tool_convert'),
      isAvailable: true
    });
  }

  async execute(input: string): Promise<ToolResult> {
    const params: ConvertParams = {
      sourcePath: input,
      targetFormat: ImageFormat.JPEG,
      quality: 90
    };
    const result: ConvertResult = await KitManager.getImageKit().convertFormat(params);
    return {
      success: result.success,
      data: result.outputPath,
      message: result.message
    };
  }
}

十、新增工具流程

10.1 零侵入扩展步骤

新增一个工具的完整流程如下:

  1. 创建工具类,继承 AbstractTool
  2. 实现 execute 方法编写核心逻辑
  3. 在 ToolRegistry.initAllTools 中添加注册调用
  4. 无需修改 Toolbox 页面、ToolCard 组件等已有代码
// 步骤1-2: 创建新工具
export class QrCodeTool extends AbstractTool {
  constructor() {
    super({
      id: 'tool_qrcode',
      name: '二维码生成',
      description: '将文本生成二维码图片',
      category: ToolCategory.UTIL,
      icon: $r('app.media.ic_tool_qrcode'),
      isAvailable: true
    });
  }

  async execute(input: string): Promise<ToolResult> {
    const qrPath: string = await QrCodeUtil.generate(input);
    return {
      success: qrPath.length > 0,
      data: qrPath,
      message: '二维码生成成功'
    };
  }
}

// 步骤3: 在 ToolRegistry.initAllTools 中添加一行注册
ToolManager.register(new QrCodeTool()); // 新增一行即可

整个新增工具过程只需编写一个新类和一行注册代码,完全不影响已有功能,体现了开闭原则的工程实践。

总结

统一 ToolManager 架构是 HarmonyExplorer 项目中插件化设计的核心实践。通过 ITool 接口定义、AbstractTool 基类复用、注册表机制和分类索引,实现了工具的零侵入式扩展。ToolHistory 历史记录和 ToolContext 数据传递机制进一步增强了工具系统的实用性。与 KitManager 的分层协作确保了业务逻辑与系统能力的清晰边界。这一架构使得工具箱模块可以持续扩展而不会导致代码腐化。更多架构设计参考请查阅 HarmonyOS 应用架构指南ArkTS 编程规范

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

相关资源

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐