Skip to content

模块化设计

概述

HCompass 的核心设计理念是模块化,以"功能包"为核心单元,通过组合不同功能包快速构建应用。这种设计使得代码更加清晰、可维护,并且功能包可以跨项目复用。

功能包(Package)

什么是功能包

功能包是 HCompass 中的核心概念,它是一个独立的业务模块,包含完整的业务逻辑、页面、服务和数据模型。

功能包的特点

  • 独立性:每个功能包都是独立的,可以单独开发、测试和部署
  • 可复用:功能包可以在不同项目间复用
  • 解耦:功能包之间通过契约解耦,不直接依赖
  • 完整性:功能包包含完整的业务逻辑,从 UI 到数据层

功能包结构

packages/
└── user/                       # 用户功能包
    ├── navigation/             # 导航构建器
    ├── services/               # 服务
    │   └── UserServiceImpl.ets
    ├── view/                   # 页面
    │   ├── UserProfilePage.ets
    │   └── UserSettingsPage.ets
    ├── viewmodels/             # ViewModel
    │   ├── UserProfileViewModel.ets
    │   └── UserSettingsViewModel.ets
    ├── models/                 # 数据模型(可选)
    │   └── UserModel.ets
    ├── components/             # 组件(可选)
    │   └── UserAvatar.ets
    └── UserModule.ets          # 功能包生命周期

依赖注入(DI)

为什么需要 DI

依赖注入是实现模块解耦的关键技术。通过 DI,功能包可以:

  • 不直接依赖其他功能包的实现
  • 通过契约(接口)获取服务
  • 方便进行单元测试(可以注入 Mock 对象)

DI 的使用

1. 定义契约

在 Shared 层定义服务契约:

typescript
// shared/contracts/IUserService.ets
export interface IUserService {
  getUserInfo(): Promise<UserInfo>;
  updateUserInfo(info: UserInfo): Promise<void>;
}

2. 实现服务

在功能包中实现服务:

typescript
// packages/user/services/UserServiceImpl.ets
import { IUserService } from "@shared/contracts";

export class UserServiceImpl implements IUserService {
  async getUserInfo(): Promise<UserInfo> {
    // 实现逻辑
  }

  async updateUserInfo(info: UserInfo): Promise<void> {
    // 实现逻辑
  }
}

3. 注册服务

在功能包的生命周期中注册服务:

typescript
// packages/user/UserModule.ets
import { UserServiceImpl } from "./services/UserService";
import { IUserService } from "@shared/contracts";

/**
 * 用户模块
 * 实现 FeatureModule 接口,支持自动注册
 */
export class UserModule implements FeatureModule {
  /**
   * 注册 DI 服务
   * @param container DI 容器
   */
  registerServices(container: Container): void {
    // 注册本模块服务
    container.register<IUserService>("IUserService", () => new UserServiceImpl());
  }
}

4. 使用服务

在其他功能包中通过 DI 获取服务:

typescript
// packages/main/viewmodels/MainViewModel.ets
import { getContainer } from "@core/di";
import { IUserService } from "@shared/contracts";

export class MainViewModel {
  private userService: IUserService;

  constructor() {
    this.userService = getContainer.resolve<IUserService>("IUserService");
  }

  async loadUserInfo(): Promise<void> {
    const userInfo = await this.userService.getUserInfo();
    // 使用用户信息
  }
}

契约层(Shared)

契约的作用

契约层定义了功能包之间的通信协议,它:

  • 定义服务接口
  • 定义数据结构
  • 定义共享状态

契约的设计原则

  • 接口隔离:每个契约只定义必要的方法
  • 稳定性:契约一旦定义,应保持稳定
  • 类型安全:充分利用 TypeScript 的类型系统

契约示例

typescript
// shared/contracts/IAuthService.ets
export interface IAuthService {
  login(username: string, password: string): Promise<LoginResult>;
  logout(): Promise<void>;
  isLoggedIn(): boolean;
}

// shared/types/AuthTypes.ets
export interface LoginResult {
  success: boolean;
  token?: string;
  message?: string;
}

功能包的生命周期

1. 创建功能包

在 packages 目录下创建新功能包。

2. 定义契约

在 Shared 层定义功能包需要对外提供的服务契约。

3. 实现功能

在功能包内实现业务逻辑、页面、服务等。

4. 注册功能包

在 Entry 层注册功能包。

5. 使用功能包

其他功能包通过 DI 注入使用该功能包提供的服务。

功能包的复用

跨项目复用

功能包可以直接复制到其他项目使用:

  1. 复制功能包目录到新项目的 packages/ 目录
  2. 复制相关的契约定义到新项目的 shared/ 目录
  3. 在新项目中注册功能包
  4. 开始使用

模块化的优势

1. 代码组织清晰

每个功能包都有明确的职责和边界,代码组织清晰。

2. 易于维护

功能包独立,修改一个功能包不会影响其他功能包。

3. 提高复用性

功能包可以在不同项目间复用,减少重复劳动。

4. 并行开发

不同团队可以并行开发不同的功能包,提高开发效率。

5. 易于测试

功能包独立,可以单独进行单元测试和集成测试。

最佳实践

1. 保持功能包的独立性

  • 功能包不应直接依赖其他功能包的实现
  • 通过契约和 DI 注入实现功能包之间的通信

2. 合理划分功能包

  • 按业务领域划分功能包
  • 每个功能包职责单一,不要过大或过小

3. 定义清晰的契约

  • 契约应该简洁明了
  • 契约应该稳定,不频繁变动

4. 使用 TypeScript 类型系统

  • 充分利用 TypeScript 的类型系统
  • 确保类型安全

5. 编写文档

  • 为每个功能包编写文档
  • 说明功能包的用途、API 和使用方法

新增模块流程

以下示例以创建 goods 模块为例,演示在 DevEco Studio 中新增模块的操作流程。

新增 feature 模块

  1. feature/ 目录上点击右键,选择 New -> Module...

创建模块入口

  1. 在模板选择界面选择 Static Library,点击 Next

选择模板

  1. 填写模块名称(如 goods)与设备类型,点击 Finish 完成创建。

配置模块信息

下一步