HarmonyOS 应用开发《掌上英语》第7篇-项目演进路线从单页面到多模块架构升级

项目演进路线:从单页面到多模块的架构升级策略

一、引言

任何一个成功的应用都不是一蹴而就的,而是从简单的单模块结构逐步演进到复杂的分层架构。本文以一个当前的 11 模块分层架构案例为蓝本,回溯其可能的演进路线------从最初的一个 entry 模块,逐步拆分为 features → commons → components 四层架构。

理解这个演进过程,能帮助开发者在自己的项目中做出恰当的架构决策:什么时候拆分?先拆什么?接口如何先行?依赖如何治理?


二、阶段一:单模块起步

2.1 初始结构

项目初始阶段,所有代码都在 product/entry 一个模块中:

复制代码
product/entry/
  src/main/ets/
    pages/         # 所有页面
    components/    # 所有组件
    models/        # 所有数据模型
    utils/         # 所有工具类
    viewmodels/    # 所有 ViewModel
  resources/

2.2 单模块的优势与痛点

优势

  • 开发效率高,无需考虑模块间通信
  • 调试简单,不存在跨模块断点问题
  • 构建速度快,无需处理多模块依赖

痛点

  • 编译速度随代码量增加而线性下降
  • 代码耦合严重,难以进行独立测试
  • 无法实现组件级别的复用
  • 团队协作时冲突频繁

2.3 何时需要拆分?

根据实践经验,出现以下信号时应该考虑拆分:

  1. 代码量信号src/ 目录超过 50 个文件,构建时间超过 3 分钟
  2. 团队信号:2 人以上同时修改同一模块,频繁产生合并冲突
  3. 复用信号:发现相同或类似的 UI 组件在多个页面中重复实现
  4. 业务信号:不同的业务功能(首页/我的/答题)边界逐渐清晰

三、阶段二:横向拆分 --- 三层架构

3.1 识别业务边界

首先分析应用的业务功能,识别出清晰的业务模块:

复制代码
- 首页(homePage):课程展示、搜索、分类浏览
- 我的(minePage):个人中心、设置、学习记录
- 答题(topicPage):答题引擎、试卷、错题本

这三个业务模块之间的交互很少,天然适合横向拆分。

3.2 建立三层架构

第一次拆分形成三层架构:

复制代码
product/entry/          ← 应用入口,承载路由表和 Tab 导航
  └── 依赖三个 feature
features/
  ├── homePage/         ← 首页业务
  ├── minePage/         ← 我的业务
  └── topicPage/        ← 答题业务

每个 feature 模块拥有自己的页面、ViewModel、模型和资源,通过 Index.ets 导出页面 Builder:

typescript 复制代码
// features/homePage/Index.ets
export { HomePageBuilder } from './src/main/ets/pages/MainPage';
export { FeaturedCoursesBuilder } from './src/main/ets/pages/FeaturedCourses';
export { SearchPageBuilder } from './src/main/ets/pages/SearchIndexPage';
// ...

3.3 路由解耦:RouterTable + RouterModule

拆分后最大的挑战是路由管理。项目采用 RouterTable 统一注册 + RouterModule 统一跳转的模式:

typescript 复制代码
// product/entry/src/main/ets/model/RouterTable.ets
import { ChapterPracticeBuilder } from 'homepage';
import { AnswerQuestionsBuilder } from 'topicpage';
import { SetUpPageBuilder } from 'minepage';

export class RouterTable {
  static builderMap: Map<string, WrappedBuilder<[object]>> = new Map();

  public static routerInit() {
    RouterTable.builderMap.set(RouterMap.MAIN_PAGE, wrapBuilder(MainEntryBuilder));
    RouterTable.builderMap.set(RouterMap.LOGIN_PAGE, wrapBuilder(LoginPageBuilder));
    RouterTable.builderMap.set(RouterMap.ANSWER_QUESTIONS_PAGE, wrapBuilder(AnswerQuestionsBuilder));
    RouterTable.builderMap.set(RouterMap.SET_UP_PAGE, wrapBuilder(SetUpPageBuilder));
    // ... 30+ 路由注册
  }
}

通过 RouterModule 封装导航操作:

typescript 复制代码
// commons/commonLib/src/main/ets/utils/RouterModule.ets
class RouterModule {
  public static stack: NavPathStack = new NavPathStack();
  static builderMap: Map<string, WrappedBuilder<[object]>> = new Map();

  public static push(info: NavRouterInfo) {
    RouterModule.stack.pushPathByName(info.url, info.param, info.onPop);
  }

  public static pop() {
    RouterModule.stack.pop();
  }

  public static getNavParam<T>(info: NavRouterInfo): T | undefined {
    const paramsArr = RouterModule.stack.getParamByName(info.url) as T[];
    return paramsArr.pop();
  }
}

这种模式的精妙之处在于:feature 模块不直接引用 RouterTable,而是通过 RouterMap 枚举引用路由名,由 entry 模块负责实际的路由注册


四、阶段三:纵向拆分 --- 提取 commons 层

4.1 发现重复代码

随着三个 feature 模块的开发,出现了一些跨模块共享的代码:

  • BreakpointModel + BreakpointType:所有模块都需要响应式布局
  • RouterModule:路由工具需要被所有模块使用
  • CommonHeader / TopBar / Banner:通用 UI 组件
  • Logger / PreferenceUtil / AudioPlayer:工具类
  • 各种 Manager 和 Model

4.2 抽取 commonLib

将这些共享代码提取为 commons/commonLib 模块:

复制代码
commons/commonLib/
  src/main/ets/
    components/       # 通用 UI 组件
      Banner.ets
      CommonHeader.ets
      TopBar.ets
    constants/        # 路由枚举
      RouterMap.ets
    managers/         # 业务管理器
      DashboardManager.ets
      ReminderManager.ets
      StatisticsManager.ets
    models/           # 通用数据模型
      BreakpointModel.ets
      SampleModel.ets
      WordCardModel.ets
    utils/            # 工具类
      AudioPlayer.ets
      BreakpointUtils.ets
      Logger.ets
      PreferenceUtil.ets
      RouterModule.ets
    viewModel/        # 通用 ViewModel
      OrderInfo.ets

通过 Index.ets 统一导出:

typescript 复制代码
// commons/commonLib/Index.ets
export { Logger } from './src/main/ets/utils/Logger';
export { BreakpointType, BreakpointModel } from './src/main/ets/models/BreakpointModel';
export { RouterModule, RouterMap } from './src/main/ets/utils/RouterModule';
export { Banner } from './src/main/ets/components/Banner';
export { BaseViewModel } from './src/main/ets/models/BaseViewModel';
export { PreferenceUtil } from './src/main/ets/utils/PreferenceUtil';
// ...

4.3 commonLib 的依赖方向

commonLib 是纯底层模块,不依赖任何 feature 或 component:

复制代码
homePage ──┐
minePage ──┼──→ commonLib
topicPage ─┘
entry ──────┘

所有依赖箭头指向 commonLib,commonLib 不反向依赖任何人。


五、阶段四:精细拆分 --- 组件层

5.1 识别可复用的业务组件

在 feature 模块的开发中,识别出可独立成组件的业务能力:

组件 识别信号 来源模块
base_select 多个页面需要选择器 homePage
select_category 分类选择逻辑可复用 homePage
answer_questions 答题引擎独立性强 topicPage
login_info 登录逻辑在各处复用 entry
aggregated_payment 支付收银台独立 minePage
feedback 反馈功能可复用 minePage
search 搜索 UI 独立 homePage
search_question 搜索题目逻辑独立 homePage

5.2 组件提取原则

提取组件时遵循"接口先行"原则:

第一步:定义接口

typescript 复制代码
// 组件提取前,先在原位置定义接口
interface SelectCategoryProps {
  categories: CategoryItem[];
  onSelect: (item: CategoryItem) => void;
  selectedId?: string;
}

第二步:实现组件

typescript 复制代码
// 创建组件模块,实现接口
@ComponentV2
export struct SelectCategory {
  @Param categories: CategoryItem[] = [];
  @Event onSelect: (item: CategoryItem) => void = () => {};
  @Param selectedId: string = '';
  // 实现细节
}

第三步:导出并在原处替换

typescript 复制代码
// 原模块中移除实现代码,改为导入
import { SelectCategory } from 'select_category';

5.3 组件的依赖策略

提取后的组件遵循"最小依赖"原则:

  • 7 个组件零外部依赖
  • feedback 组件依赖 axios 等网络库
  • 组件不依赖 commonLib(避免与应用业务耦合)

这种设计使得组件可以脱离本项目,直接复用到其他 HarmonyOS 应用中。


六、最终架构全景

经过四个阶段的演进,项目形成了完整的四层架构:

复制代码
┌─────────────────────────────────────────────────────────┐
│                    product/entry                         │
│  路由表(RouterTable)、Tab 导航、Ability 生命周期       │
│  依赖:所有 features + components                        │
├─────────────────────────────────────────────────────────┤
│                    features 层                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ homePage │  │ minePage │  │topicPage │              │
│  │ 首页业务  │  │ 我的业务  │  │ 答题业务  │              │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘              │
├───────┼──────────────┼──────────────┼───────────────────┤
│       └──────┬───────┘              │                   │
│              ▼                      ▼                   │
│          commons/commonLib                              │
│   通用组件、模型、工具类、路由、资源                     │
│   被所有 features、components、entry 依赖               │
├─────────────────────────────────────────────────────────┤
│                    components 层                         │
│  base_select  select_category  answer_questions         │
│  login_info   aggregated_payment  feedback              │
│  search       search_question                            │
│  独立 HAR 包,零依赖或极少依赖,可跨项目复用              │
└─────────────────────────────────────────────────────────┘

模块间的依赖关系通过 build-profile.json5 清晰体现:

json5 复制代码
{
  "modules": [
    { "name": "entry", "srcPath": "./product/entry" },
    { "name": "homePage", "srcPath": "./features/homePage" },
    { "name": "minePage", "srcPath": "./features/minePage" },
    { "name": "topicPage", "srcPath": "./features/topicPage" },
    { "name": "commonLib", "srcPath": "./commons/commonLib" },
    { "name": "base_select", "srcPath": "./components/base_select" },
    // ... 其余 7 个组件
  ]
}

七、渐进式重构策略

7.1 重构原则

  1. 每次只改一层:先做横向拆分(feature),稳定后再做纵向拆分(commons)
  2. 接口先行:先定义接口和导出,再移动实现
  3. 持续构建可工作的版本:每一步拆分后都要确保应用可以正常构建和运行
  4. 小步提交:每次重构提交一个独立的模块,而不是一次性提交所有变更

7.2 重构优先级建议

复制代码
第一阶段(1-2 天):提取 commonLib
  ├── 通用工具类(Logger、PreferenceUtil)
  ├── 通用模型(BreakpointModel)
  └── 通用组件(Banner、TopBar)

第二阶段(2-3 天):拆分为三个 feature
  ├── homePage(首页相关页面)
  ├── minePage(我的相关页面)
  └── topicPage(答题相关页面)

第三阶段(3-5 天):提取 8 个组件
  ├── 先拆分零依赖组件(base_select、search 等)
  └── 再拆分有依赖组件(feedback)

7.3 依赖治理的检查清单

拆分后,定期检查以下依赖规则是否被违反:

  • entry 依赖所有模块,但没有任何模块依赖 entry
  • feature 模块依赖 commonLib,但不依赖其他 feature
  • component 模块不依赖任何 feature
  • commonLib 不依赖任何模块(纯底层)
  • 无循环依赖链(A → B → A)

八、总结:架构演进的关键决策

阶段 决策 收益
单模块 快速验证业务 开发效率最大化
横向拆分 按业务边界拆为三个 feature 团队并行开发,构建速度提升
纵向拆分 提取 commonLib 消除重复代码,统一基础能力
组件化 提取 8 个独立组件 支持跨项目复用,极致解耦

架构演进不是一蹴而就的工程,而是随着业务发展和团队规模变化持续调整的过程。理解何时拆分、拆到什么粒度、用什么策略拆,比理解最终架构本身更重要。

对于正在规划 HarmonyOS 应用架构的团队,建议从单模块起步,在出现明确的代码复用需求和团队协作痛点时,按照本文的四个阶段逐步演进,最终构建出适合自己业务的分层架构。


九、参考源码路径

文件 路径
根模块配置 d:\HarmonyOS\WorkSpace\Exam1.0.4\build-profile.json5
RouterTable d:\HarmonyOS\WorkSpace\Exam1.0.4\product\entry\src\main\ets\model\RouterTable.ets
RouterMap d:\HarmonyOS\WorkSpace\Exam1.0.4\commons\commonLib\src\main\ets\constants\RouterMap.ets
RouterModule d:\HarmonyOS\WorkSpace\Exam1.0.4\commons\commonLib\src\main\ets\utils\RouterModule.ets
commonLib Index d:\HarmonyOS\WorkSpace\Exam1.0.4\commons\commonLib\Index.ets
homePage Index d:\HarmonyOS\WorkSpace\Exam1.0.4\features\homePage\Index.ets
minePage Index d:\HarmonyOS\WorkSpace\Exam1.0.4\features\minePage\Index.ets
topicPage Index d:\HarmonyOS\WorkSpace\Exam1.0.4\features\topicPage\Index.ets
homePage oh-package d:\HarmonyOS\WorkSpace\Exam1.0.4\features\homePage\oh-package.json5
entry oh-package d:\HarmonyOS\WorkSpace\Exam1.0.4\product\entry\oh-package.json5
相关推荐
ai_coder_ai1 小时前
论模型驱动分析方法及应用
微服务·云原生·架构
咖啡八杯1 小时前
GoF设计模式——状态模式
java·面试·架构
Briwisdom1 小时前
LLM 推理引擎架构:vLLM / SGLang 的核心设计
架构·vllm·sglang·pagedattention·radixattention
禅思院2 小时前
AI对话前端从入门到崩溃:一个长对话引发的五层优化战争【五】
前端·架构·前端框架
herosunly11 小时前
60ms 不是启动快,而是不用重新启动:CubeSandbox 极速冷启动架构拆解
架构·cube sandbox
zlinear数据采集卡13 小时前
从气动比例阀到任意波形:硬核拆解ZLinear采集卡的DAC输出架构与工业闭环控制实战
arm开发·架构
2601_9619460815 小时前
AI API 网关实战:从单 Key 管理到企业级多租户架构
大数据·人工智能·金融·架构·api·个人开发
XUHUOJUN16 小时前
Windows 2025架构深度分析之Stretch Cluster 延迟工程现实
windows·microsoft·架构·azure local
Web极客码17 小时前
突破并发瓶颈:云端高性能架构如何赋能海外 AI Agent 矩阵的高效产出
服务器·人工智能·架构