HarmonyOS应用《民族图鉴》开发第46篇:全书总结——最佳实践回顾与项目演进路线

一、一路走来
从第1篇到第46篇,我们一起走过了一段完整的 HarmonyOS 开发之旅,覆盖了民族图鉴应用的方方面面:
| 篇章 | 篇数 | 核心内容 |
|---|---|---|
| 入门基础篇 | 第1-10篇(10篇) | HarmonyOS 基础、ArkTS 语法、开发环境搭建 |
| 页面开发篇 | 第11-30篇(20篇) | 从启动页到 AI 对话页,10+ 核心页面的设计与实现 |
| 数据与服务篇 | 第31-40篇(10篇) | 数据模型、Mock 数据、7 大核心服务 |
| 架构与工程篇 | 第41-46篇(6篇) | 整体架构、组件化、性能、工程化、调试、总结 |
这最后一篇,我们一起回顾一下整个系列的核心要点,并聊聊项目未来可以怎么演进。
二、完整知识图谱
46 篇文章,构建了一个完整的 HarmonyOS 开发知识体系。让我们用一张图来梳理:
HarmonyOS 开发知识体系
│
┌──────────────────────┼──────────────────────┐
│ │ │
基础语法层 应用开发层 工程实践层
│ │ │
┌────┴────┐ ┌──────┴──────┐ ┌────┴────┐
│ ArkTS │ │ UI 组件 │ │ 架构设计 │
│ 基础语法 │ │ 页面开发 │ │ 设计模式 │
│ 类型系统 │ │ 状态管理 │ │ 分层架构 │
└─────────┘ └──────┬──────┘ └────┬────┘
│ │
┌────┴────┐ ┌────┴────┐
│ 数据服务 │ │ 性能优化 │
│ 网络请求 │ │ 内存优化 │
│ 本地存储 │ │ 渲染优化 │
└────┬────┘ └────┬────┘
│ │
┌────┴────┐ ┌────┴────┐
│ 能力扩展 │ │ 工程化 │
│ 多媒体 │ │ 测试 │
│ TTS/AI │ │ CI/CD │
└─────────┘ └────┬────┘
│
┌────┴────┐
│ 调试排错 │
│ 质量保障 │
└─────────┘
2.1 六大篇章核心要点详细回顾
46 篇文章,六大篇章,每一篇都有其核心价值。让我们逐篇章回顾:
篇章一:入门基础篇(第 1-10 篇)
核心目标:搭建开发环境,掌握 ArkTS 基础语法,写出第一个 HarmonyOS 应用。
核心知识点:
- HarmonyOS 系统概述:分布式理念、应用模型、系统架构
- ArkTS 基础语法:变量、类型、函数、类、接口、泛型
- 开发环境搭建:DevEco Studio 安装、项目结构、运行调试
- 第一个应用:Hello World、页面结构、基础组件
关键收获:
- 理解 HarmonyOS 的"分布式"理念
- 掌握 ArkTS 的类型系统(比 JS 更安全,比 Java 更灵活)
- 跑通第一个应用,建立信心
篇章二:页面开发篇(第 11-30 篇)
核心目标:掌握常用组件和页面开发模式,能独立完成 10+ 页面的开发。
核心知识点:
- 基础组件:Text、Image、Button、List、Grid、Scroll…
- 布局系统:Flex、Column、Row、Stack、RelativeContainer
- 状态管理:@State、@Prop、@Link、@Provide/@Consume
- 页面路由:Router、Navigation、页面栈
- 列表优化:LazyForEach、复用机制、性能优化
- 组件化:自定义组件、@Builder、@Extend
- 20 个页面实战:启动页、引导页、首页、列表页、详情页、设置页…
关键收获:
- 掌握"声明式 UI"的开发范式
- 理解 ArkUI 的响应式状态管理机制
- 积累 20 个页面的实战经验
- 形成组件化、复用的思维
篇章三:数据与服务篇(第 31-40 篇)
核心目标:掌握数据模型设计、服务层架构、7 大核心服务的实现。
核心知识点:
- 数据模型设计:56 个民族数据结构、平行字段双语设计
- Mock 数据:数据生成、数据分层、前后端分离
- 7 大核心服务:
- StorageService:本地持久化存储
- TTSEngineService:文本转语音
- MusicService:音乐播放
- ThemeService:主题切换
- I18nService:多语言国际化
- AIService:AI 对话问答
- ApiService:数据 API 封装
- 设计模式:单例、监听者、策略、外观、适配器、状态
关键收获:
- 理解"薄页面,厚服务"的架构思想
- 掌握服务层的设计方法
- 学会用设计模式解决实际问题
- 建立业务逻辑与 UI 分离的意识
篇章四:架构与工程篇(第 41-46 篇)
核心目标:建立系统化的架构思维和工程化思维,提升代码质量和开发效率。
核心知识点:
- 整体架构:四层架构(表现层、服务层、数据层、基础设施层)
- 组件化架构:组件拆分、组件通信、组件库建设
- 性能优化:启动优化、列表优化、内存优化、渲染优化
- 工程化实践:代码规范、Lint、测试、构建、CI/CD
- 调试方法论:科学调试流程、二分法、日志调试、断点调试
- Git 与版本管理:分支模型、提交规范、代码评审
关键收获:
- 建立系统化的架构思维
- 掌握性能优化的方法论
- 形成工程化的开发习惯
- 提升调试和排错能力
💡 六大篇章的递进关系:
入门 → 页面 → 服务 → 架构 → 工程 → 总结
从"能写出来"到"写得好",再到"设计得好"、“维护得好”——
这是一个从"术"到"道"的过程,也是一个程序员成长的必经之路。
2.2 学习路径建议
如果你是从头开始学,推荐的学习路径是:
第一阶段:打基础(第1-10篇)
- 了解 HarmonyOS 是什么、能做什么
- 掌握 ArkTS 基本语法
- 搭好开发环境,跑起来第一个 Hello World
第二阶段:做页面(第11-30篇)
- 跟着做 10+ 个页面
- 掌握常用组件的用法
- 理解状态管理和页面跳转
- 这时候你已经能做出像样的 App 了
第三阶段:做服务(第31-40篇)
- 学习数据模型设计
- 掌握 7 大核心服务的设计思路
- 理解业务逻辑和 UI 分离
- 这时候你能做出结构清晰的 App 了
第四阶段:练内功(第41-46篇)
- 学习架构设计思想
- 掌握性能优化方法
- 建立工程化思维
- 提升调试和排错能力
- 这时候你能做出高质量、可维护的 App 了
💡 学习的真相:
学习不是线性的——不是学完 46 篇就"出师"了。
真正的学习是:学一遍 → 做项目 → 遇到问题 → 回来再看 → 有新的体会 → 再做项目 → …
这是一个循环上升的过程。每多做一个项目,回头看这些文章,都会有新的收获。
三、46 篇文章详细索引
46 篇文章,四大篇章,构成了一条从入门到进阶的完整学习路径。下面按篇章分组,每篇文章一句话概括核心内容,方便你按需查阅。
3.1 入门基础篇(第 1-10 篇,10 篇)
入门是地基——打好基础,后面才能走得稳。
| 序号 | 文章标题 | 核心内容 |
|---|---|---|
| 01 | 鸿蒙开发初体验:从 0 到 1 跑通第一个应用 | DevEco Studio 安装、项目创建、Hello World、运行到真机/模拟器 |
| 02 | ArkTS 基础语法:变量、类型与基本数据类型全解析 | 变量声明、数据类型、类型推断、类型断言、基础语法规则 |
| 03 | 函数与类:从面向过程到面向对象的思维转变 | 函数定义、参数、返回值、类、继承、接口、面向对象基础 |
| 04 | 数组与对象:数据集合的常用操作 | 数组方法、对象操作、解构赋值、扩展运算符、常用技巧 |
| 05 | 条件与循环:控制流程的基石 | if/else、switch、for、while、break、continue、控制流控制 |
| 06 | 声明式 UI 初体验:ArkUI 开发范式入门 | 声明式 vs 命令式、UI 描述、链式调用、基本组件 |
| 07 | 基础组件大全:Text/Image/Button/Icon 全解析 | 四大基础组件的属性、事件、使用场景、实战用法 |
| 08 | 容器组件:Column/Row/Stack 布局三剑客 | 横向、纵向、堆叠三种基础布局容器、对齐方式、间距 |
| 09 | 状态管理入门:@State 与组件内状态 | @State 装饰器、状态驱动 UI、状态变化触发刷新 |
| 10 | 页面路由与页面跳转:多页面应用的基础 | Router API、页面跳转、参数传递、返回、返回值、页面栈 |
3.2 页面开发篇(第 11-30 篇,20 篇)
页面是用户直接面对用户的部分,也是 ArkUI 最核心的内容。
| 序号 | 文章标题 | 核心内容 |
|---|---|---|
| 11 | 列表组件 List:长列表渲染与性能优化 | List 组件、ListItem、LazyForEach、下拉刷新、上拉加载 |
| 12 | 网格布局 Grid:宫格列表的实现 | Grid 组件、GridItem、网格布局、自适应、性能优化 |
| 13 | 滚动组件 Scroll:可滚动页面的打造 | Scroll 组件、滚动方向、滚动控制、滚动监听 |
| 14 | 导航组件 Navigation:统一的导航栏 | Navigation 组件、标题栏、返回按钮、自定义导航栏 |
| 15 | 标签页 Tabs:多标签页切换 | Tabs 组件、TabContent、标签切换、懒加载 |
| 16 | 表单组件:输入框/开关/滑块/复选框/单选框 | 常用表单组件大全、表单验证、数据收集 |
| 17 | 弹窗组件:对话框/Toast/自定义弹窗 | AlertDialog、Toast、自定义弹窗、弹窗交互 |
| 18 | 动画基础:属性动画与转场动画 | 属性动画、动画曲线、动画时长、转场动画 |
| 19 | 自定义组件:组件封装与复用 | @Component、自定义组件、props、events |
| 20 | @Builder 与 @Extend:UI 抽与样式封装 | @Builder UI、@Extend 样式扩展、复用技巧 |
| 21 | 状态管理进阶:@Prop/@Link/@Provide/@Consume | 父子组件通信、跨组件状态、跨层级状态传递 |
| 22 | 组件生命周期:组件的一生 | aboutToAppear/aboutToDisappear/onPageShow/onPageHide |
| 23 | 民族列表页实战:从 0 到 1 打造列表页 | 搜索栏、列表、下拉刷新、上拉加载、完整实战 |
| 24 | 民族详情页实战:丰富的信息展示 | 顶部、基本信息卡、文化信息卡、多卡片布局 |
| 25 | 搜索功能实战:搜索栏 + 搜索历史 + 搜索结果 | 搜索功能实现、搜索历史、搜索结果展示、搜索优化 |
| 26 | 收藏功能实战:收藏列表 + 收藏管理 | 收藏按钮、收藏列表、收藏状态管理、本地存储 |
| 27 | 主题切换实战:浅色/深色主题切换 | 主题定义、主题切换、全局主题、全局主题适配 |
| 28 | 多语言国际化:中/英双语切换 | 多语言资源、语言切换、双语字段设计、i18n |
| 29 | 音乐播放页实战:音乐播放器 | 播放控件、进度条、播放列表、播放状态 |
| 30 | 设置页实战:设置列表 + 各功能开关 + 关于 | 设置列表、各种设置项、关于页、设置项组织 |
3.3 数据与服务篇(第 31-40 篇,10 篇)
数据是应用的血液,服务是应用的骨架。
| 序号 | 文章标题 | 核心内容 |
|---|---|---|
| 31 | 数据模型设计:56 个民族的数据结构 | 数据模型定义、interface、类型定义、双语设计、数据结构 |
| 32 | Mock 数据:前后端分离开发利器 | Mock 数据生成、数据分层、前后端分离、Mock 开发模式 |
| 33 | 本地持久化 Preferences:轻量存储方案 | Preferences API、封装、收藏/历史/设置存储、持久化 |
| 34 | StorageService 封装:统一的存储服务封装 | Service 单例模式、封装、统一封装、统一 API |
| 35 | TTS 文本转语音:让文字"说出来" | TTS 引擎、朗读、暂停、停止、状态管理 |
| 36 | MusicService:音乐播放服务封装 | AVPlayer、播放控制、播放列表、进度管理 |
| 37 | 网络请求:HTTP 请求与数据解析 | HTTP 请求、JSON 数据解析、异常处理、统一封装 |
| 38 | ApiService 设计:Mock 与远程 API 无缝切换 | 策略模式、防腐层、降级、数据转换 |
| 39 | AI 对话功能:智能问答的实现 | 对话 UI、对话逻辑、上下文管理、Mock AI |
| 40 | 服务层整体架构:7 大服务协同工作 | 服务层设计、服务间通信、统一管理、服务整体架构 |
3.4 架构与工程篇(第 41-46 篇,6 篇)
内功心法——决定代码质量和可维护性的核心。
| 序号 | 文章标题 | 核心内容 |
|---|---|---|
| 41 | 四层分层架构:表现层 / 服务层 / 数据层 / 基础设施层 | 分层架构设计、各层职责、依赖关系、分层原则 |
| 42 | 性能优化:启动速度、、内存、网络全方位优化 | 启动优化、内存优化、网络优化、列表优化、图片优化 |
| 43 | 代码规范与工程化:可维护代码的写法 | 命名规范、代码风格、目录结构、模块化、工程化 |
| 44 | 单元测试与代码质量:测试是重构的底气 | 单元测试、测试框架、Mock、测试覆盖率、质量保证 |
| 45 | 调试方法论:科学排错的艺术 | 调试流程、二分法、日志调试、断点调试、调试工具、调试心态 |
| 46 | 全系列总结:从入门到进阶的完整知识图谱 | 知识图谱、架构总结、功能清单、最佳实践、成长路径 |
📚 如何使用这份索引:
- 入门学习:按顺序从第 1 篇看到第 46 篇,循序渐进。
- 按需查阅:遇到具体问题时,根据索引快速定位到对应文章。
- 复习回顾:做完项目后,回过头来看相关文章,会有新的体会。
- 项目实战:边做项目边查文章,在实践中加深理解。
四、架构设计总览
3.1 四层架构
整个项目采用经典的分层架构:
┌─────────────────────────────────────────────┐
│ 表现层(Presentation) │
│ 页面 / 组件 / UI 逻辑 │
│ 薄页面,厚服务 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 服务层(Service) │
│ 业务逻辑 / 状态管理 / 能力封装 │
│ 单例模式 / 监听者模式 / 策略模式 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 数据层(Data) │
│ 模型定义 / Mock 数据 / API 数据 │
│ 类型安全 / 平行字段双语设计 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 基础设施层(Infrastructure) │
│ 工具函数 / 常量 / 系统能力封装 │
│ HttpClient / LazyDataSource / 常量 │
└─────────────────────────────────────────────┘
核心设计原则:
- 上层依赖下层,下层不依赖上层
- 每一层只和相邻层打交道
- 同层职责清晰,互不干扰
2.2 七大服务
服务层是整个应用的"脊梁",一共 7 个核心服务:
| 服务 | 职责 | 核心模式 |
|---|---|---|
| StorageService | 本地持久化存储 | 单例 + 泛型 + 业务方法封装 |
| TTSEngineService | 文本转语音 | 单例 + 状态机 + 降级策略 |
| MusicService | 音乐播放 | 单例 + 监听者模式 + 进度管理 |
| ThemeService | 主题切换 | 单例 + AppStorage + 三层结构 |
| I18nService | 多语言国际化 | 单例 + 平行字段 + 语言切换 |
| AIService | AI 对话问答 | 单例 + 上下文管理 + Mock 回复 |
| ApiService | 数据 API 封装 | 单例 + 策略模式 + 防腐层 + 降级 |
服务层的共性:
- 全部采用单例模式
- 封装系统/第三方能力
- 对外提供稳定的接口
- 有错误处理和降级策略
- 状态集中管理
3.3 六大设计模式
整个项目反复用到了 6 个经典设计模式:
| 模式 | 应用场景 | 核心思想 |
|---|---|---|
| 单例模式 | 所有服务 | 全局唯一实例,统一管理 |
| 监听者模式 | MusicService、AppStorage | 一对多依赖,状态变化自动通知 |
| 策略模式 | ApiService(Mock/Remote)、TTS(真实/模拟) | 多种实现,统一接口,可互换 |
| 外观模式 | 所有服务都是外观 | 封装复杂的底层,提供简单的接口 |
| 适配器模式 | ApiService 数据映射(防腐层) | 把不兼容的接口转成期望的接口 |
| 状态模式 | TTS 状态管理、播放状态 | 状态决定行为,状态变化行为变化 |
💡 设计模式的真谛:
设计模式不是"模板",不是"必须套进去"的东西。它是"前人总结的、针对某类问题的成熟解决方案"。
当你遇到类似问题时,可以参考这些模式,但不要为了用模式而用模式。好的代码是"刚好解决了问题,并且恰好符合某个模式",而不是"为了符合模式,把代码绕来绕去"。
3.4 架构原则的实践总结
除了设计模式,整个项目还贯穿了一些核心的架构原则。这些原则比具体的模式更重要——模式是"招式",原则是"内功"。
原则一:单一职责(SRP)
一个类/函数/组件,只做一件事。
这是最基础、也是最重要的原则。整个项目处处体现着这个原则:
- StorageService:只负责数据存储,不管业务逻辑
- ThemeService:只负责主题切换,不管页面怎么渲染
- 每个页面组件:只负责 UI 渲染和用户交互,业务逻辑都下沉到服务
- 每个工具函数:只做一件事,输入输出明确
为什么单一职责这么重要?
- 好维护:改一个功能,只需要改一个地方
- 好测试:功能单一,测试用例也好写
- 好复用:职责越单一,越容易被复用
- 好理解:看名字就知道它是干什么的
原则二:开闭原则(OCP)
对扩展开放,对修改关闭。
加新功能的时候,尽量加新代码,而不是改老代码。
项目中的体现:
- ApiService 的策略模式:加新的数据源(比如从 Mock 改成真实 API),加个新策略就行,不用改原有逻辑
- 主题系统:加新主题,加套配置就行,不用改主题切换的逻辑
- 组件设计:通过 props 扩展功能,而不是每次都改组件内部
原则三:依赖倒置(DIP)
依赖抽象,不依赖具体实现。
高层模块不应该依赖低层模块,两者都应该依赖抽象。
项目中的体现:
- 服务依赖接口:比如 TTSEngineService 依赖抽象的 TTS 引擎接口,不依赖具体的引擎实现
- Mock/Real 切换:ApiService 的 Mock 和真实 API,对外接口是一样的,可以无缝切换
- 防腐层:外部 API 的数据结构,通过防腐层转换后,业务代码不依赖外部结构
原则四:最少知识(迪米特法则)
一个对象应该对其他对象有最少的了解。
只和你的"朋友"说话,不要和"陌生人"说话。
项目中的体现:
- 页面只和服务打交道:页面不直接操作存储、不直接发网络请求,都通过 Service
- 服务之间低耦合:每个服务只做自己的事,需要其他服务的能力就通过明确的接口调用
- 组件 props 精简:传给组件的 props 越少越好,组件知道的越少越好
原则五:高内聚低耦合
高内聚:相关的功能放在一起
低耦合:模块之间的依赖越少越好
这是所有架构原则的终极目标。
项目中的体现:
- 每个服务内聚性很强:StorageService 里所有方法都和存储相关
- 服务之间耦合很低:改 StorageService 的实现,不影响 ThemeService
- 页面和服务解耦:页面只依赖服务接口,服务内部怎么实现的,页面不关心
原则六:DRY(Don’t Repeat Yourself)
不要重复你自己。
相同的逻辑不要写两遍——写一遍,复用它。
项目中的体现:
- 通用组件抽取:NavBar、Card、EmptyState 等组件,在多个页面复用
- 工具函数抽取:日期格式化、字符串处理、防抖节流等,抽到 utils 里
- 服务逻辑复用:业务逻辑放在 Service 里,多个页面调用同一份逻辑
DRY 不只是"少写代码",更重要的是——修改的时候只需要改一个地方。
原则七:KISS(Keep It Simple, Stupid)
保持简单。
能简单解决的,就不要搞复杂。简单的代码更容易理解、更容易维护、出了问题也更容易排查。
项目中的体现:
- 状态管理:能用 AppStorage 就不用复杂的状态管理库
- 数据存储:简单的 key-value 就用 Preferences,不用上数据库
- 页面设计:一个页面就做一件事,不要搞成"万能页面"
⚠️ 注意:KISS 不是"偷懒",不是"能跑就行"。
它的意思是:用最简单的方案解决问题,而不是"用最复杂的方案显得我厉害"。
原则八:YAGNI(You Aren’t Gonna Need It)
你不会需要它的。
不要为"可能的未来需求"提前写代码——十有八九,那个需求永远不会来,而你写的代码反而成了负担。
项目中的体现:
- v1.0 不做 v2.0 的事:v1.0 就做核心功能,不要一上来就搞模块化、微前端
- 不过度设计:不要为了"扩展性"加一堆没用的抽象
- 不提前优化:没有性能问题就不要瞎优化,先让功能跑起来
YAGNI 不是"不考虑未来",而是——只在真正需要的时候才加。需要的时候再加,比提前写了但没用要好得多。
🎯 架构原则的价值:
设计模式会变、框架会变、语言会变,但这些架构原则是不变的。
把这些原则吃透了,不管用什么框架、做什么项目,你都能写出好代码。
这就是"内功"——招式千变万化,内功万变不离其宗。
3.5 设计模式应用总结
说完了架构原则,我们再来看看项目中用到的设计模式。架构原则是"内功",设计模式就是"招式"——针对特定问题的成熟解决方案。
「民族图鉴」项目中反复用到了 6 个经典设计模式,覆盖了创建型、行为型、结构型三大类。
3.5.1 创建型模式
创建型模式关注"对象怎么创建"——封装创建逻辑,让使用者不用关心对象是怎么来的。
单例模式(Singleton)
定义:保证一个类只有一个实例,并提供一个全局访问点。
为什么用单例:
- 有些对象全局只需要一份(比如配置、服务、管理器)
- 避免重复创建,节省资源
- 统一管理状态,不会出现多个实例状态不一致
项目中的应用:
项目中的 7 大核心服务,全部采用单例模式:
- StorageService:存储服务
- TTSEngineService:TTS 引擎服务
- MusicService:音乐播放服务
- ThemeService:主题切换服务
- I18nService:多语言服务
- AIService:AI 对话服务
- ApiService:API 数据服务
代码示例(StorageService):
export class StorageService {
// 私有静态实例
private static instance: StorageService;
// 私有构造函数,外部不能 new
private constructor() {}
// 全局唯一访问点
public static getInstance(): StorageService {
if (!StorageService.instance) {
StorageService.instance = new StorageService();
}
return StorageService.instance;
}
// ... 其他方法
}
// 使用方式
const storage = StorageService.getInstance();
适用场景:
- 全局状态管理(主题、语言、用户信息)
- 资源共享(网络请求、数据库连接)
- 服务层封装(统一入口,统一管理)
- 配置管理(全局配置、环境配置)
单例的注意事项:
- 注意线程安全(多线程环境下)
- 注意内存泄漏(单例生命周期和App一样长)
- 不要滥用(不是所有东西都要做成单例)
💡 单例的本质:
单例不是"全局变量"的代名词。它的核心价值是——保证唯一性 + 统一管理。
7 大服务用单例,是因为它们需要全局唯一、需要统一管理状态、需要被多个页面共享。
3.5.2 行为型模式
行为型模式关注"对象之间怎么交互"——怎么让多个对象协作完成任务。
监听者模式(Observer)
定义:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。
别名:观察者模式、发布-订阅模式
为什么用监听者模式:
- 一个对象的状态变化,需要通知多个其他对象
- 被观察者和观察者之间解耦,互相不知道对方的存在
- 可以随时添加/删除观察者,灵活扩展
项目中的应用:
- MusicService 的播放状态监听
- AppStorage 的状态变化通知
- ThemeService 的主题变化通知
代码示例(MusicService):
// 1. 定义监听器接口
export type PlayerStateListener = (state: PlayerState) => void;
export class MusicService {
// 2. 用 Set 保存所有监听器
private listeners: Set<PlayerStateListener> = new Set();
// 3. 添加监听器
addListener(listener: PlayerStateListener): void {
this.listeners.add(listener);
}
// 4. 移除监听器
removeListener(listener: PlayerStateListener): void {
this.listeners.delete(listener);
}
// 5. 状态变化时通知所有监听器
private notifyListeners(): void {
const state: PlayerState = {
currentMusic: this.getCurrentMusic(),
isPlaying: this.isPlaying,
currentPosition: this.currentPosition,
playMode: this.playMode,
playlist: this.playlist,
currentIndex: this.currentIndex
};
this.listeners.forEach(listener => {
try {
listener(state);
} catch (e) {
console.error('MusicService listener error:', JSON.stringify(e));
}
});
}
// 状态变化时调用 notifyListeners
play(): void {
this.isPlaying = true;
this.notifyListeners(); // 状态变了,通知所有人
}
pause(): void {
this.isPlaying = false;
this.notifyListeners(); // 状态变了,通知所有人
}
}
// 使用示例
// 音乐页面:监听播放状态,更新UI
const musicService = MusicService.getInstance();
musicService.addListener((state) => {
this.isPlaying = state.isPlaying;
this.currentMusic = state.currentMusic;
});
// 详情页:也监听播放状态,显示播放按钮
musicService.addListener((state) => {
this.isPlaying = state.isPlaying;
});
适用场景:
- 一个状态变化,多个地方需要响应
- 事件通知、消息推送
- 跨页面/跨组件通信
- 解耦事件源和事件处理者
💡 监听者模式的价值:
没有监听者模式的话,MusicService 要知道谁在听它,要主动去调用每个监听者的方法——耦合度很高。
有了监听者模式,MusicService 只负责"我状态变了,我发个通知",至于谁在听、听了做什么,它完全不关心。
这就是解耦——双方只依赖抽象的接口,不依赖具体实现。
策略模式(Strategy)
定义:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
为什么用策略模式:
- 有多种实现方式,需要能灵活切换
- 把不同的实现封装起来,互不干扰
- 新增实现时不用修改原有代码(符合开闭原则)
项目中的应用:
- ApiService 的 Mock / Remote 数据切换
- TTSEngineService 的真实引擎 / 模拟模式切换
- 主题切换(浅色/深色/跟随系统)
代码示例(ApiService):
// 1. 定义策略接口(数据源)
interface DataSource {
getEthnicList(page: number, pageSize: number): Promise<EthnicGroup[]>;
getEthnicById(id: string): Promise<EthnicGroup | null>;
searchEthnics(keyword: string): Promise<EthnicGroup[]>;
}
// 2. 具体策略:Mock 数据源
class MockDataSource implements DataSource {
async getEthnicList(page: number, pageSize: number): Promise<EthnicGroup[]> {
const start = (page - 1) * pageSize;
return ETHNIC_GROUPS.slice(start, start + pageSize);
}
// ... 其他方法
}
// 3. 具体策略:远程 API 数据源
class RemoteDataSource implements DataSource {
private httpClient: HttpClient;
async getEthnicList(page: number, pageSize: number): Promise<EthnicGroup[]> {
const response = await this.httpClient.get(
`/ethnics?page=${page}&pageSize=${pageSize}`
);
return this.mapApiToModel(response.data.ethnics);
}
// ... 其他方法
}
// 4. 上下文(Context):使用策略的类
export class ApiService {
private currentDataSource: DataSource; // 当前策略
// 设置策略(切换数据源)
setDataSource(mode: DataSourceMode): void {
if (mode === DataSourceMode.MOCK) {
this.currentDataSource = new MockDataSource();
} else {
this.currentDataSource = new RemoteDataSource();
}
}
// 使用策略(具体用哪个策略,调用者不关心)
async getEthnicList(page: number, pageSize: number): Promise<EthnicGroup[]> {
return this.currentDataSource.getEthnicList(page, pageSize);
}
}
适用场景:
- 多种实现方式,需要灵活切换
- 算法/策略需要独立于使用者变化
- 需要在运行时动态切换不同的实现
- 避免大量的 if-else / switch-case
💡 策略模式的本质:
策略模式不是"为了用模式而用模式"。它的核心是——把"变"的部分封装起来,让"不变"的部分稳定。
ApiService 中,"数据从哪来"是会变的(Mock / 远程),但"怎么用数据"是不变的。
把"变"的部分封装成策略,切换策略就不会影响"不变"的部分。
状态模式(State)
定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来好像修改了它的类。
为什么用状态模式:
- 对象的行为取决于它的状态(不同状态下行为不同)
- 避免大量的 if-else / switch-case 判断状态
- 状态转换逻辑更清晰,每个状态的行为内聚在自己的类里
项目中的应用:
- TTS 播放状态管理(空闲/播放中/暂停/错误)
- 音乐播放状态机
- 页面加载状态(加载中/成功/失败/空数据)
代码示例(TTS 状态机):
// 1. 定义状态枚举
enum TTSState {
IDLE = 'idle', // 空闲
PLAYING = 'playing', // 播放中
PAUSED = 'paused', // 暂停
ERROR = 'error' // 错误
}
// 2. 状态接口
interface TTSStateHandler {
speak(text: string): boolean; // 能不能播放
pause(): boolean; // 能不能暂停
resume(): boolean; // 能不能恢复
stop(): boolean; // 能不能停止
}
// 3. 具体状态:空闲状态
class IdleState implements TTSStateHandler {
speak(text: string): boolean {
return true; // 空闲时可以播放
}
pause(): boolean {
return false; // 空闲时不能暂停
}
resume(): boolean {
return false; // 空闲时不能恢复
}
stop(): boolean {
return false; // 空闲时不能停止
}
}
// 4. 具体状态:播放中状态
class PlayingState implements TTSStateHandler {
speak(text: string): boolean {
return false; // 播放中不能重新播放(要先停止)
}
pause(): boolean {
return true; // 播放中可以暂停
}
resume(): boolean {
return false; // 播放中不需要恢复
}
stop(): boolean {
return true; // 播放中可以停止
}
}
// 5. 上下文(Context)
export class TTSEngineService {
private state: TTSState = TTSState.IDLE;
private stateHandlers: Map<TTSState, TTSStateHandler> = new Map();
constructor() {
this.stateHandlers.set(TTSState.IDLE, new IdleState());
this.stateHandlers.set(TTSState.PLAYING, new PlayingState());
// ... 其他状态
}
speak(text: string): boolean {
const handler = this.stateHandlers.get(this.state);
if (!handler || !handler.speak(text)) {
return false; // 当前状态不允许播放
}
// 执行播放逻辑
this.state = TTSState.PLAYING;
return true;
}
pause(): boolean {
const handler = this.stateHandlers.get(this.state);
if (!handler || !handler.pause()) {
return false;
}
// 执行暂停逻辑
this.state = TTSState.PAUSED;
return true;
}
}
适用场景:
- 对象的行为取决于状态,且状态数量有限
- 有大量的状态判断逻辑(if-else / switch)
- 状态转换逻辑复杂,需要集中管理
- 不同状态下的行为差异较大
💡 状态模式 vs if-else:
不用状态模式的话,每个方法里都要写一堆if (state === 'playing') { ... } else if (state === 'paused') { ... }。
状态少的时候还好,状态多了、逻辑复杂了,代码就变成"面条"了。
状态模式把每个状态的逻辑封装到自己的类里,清晰、易维护、易扩展。
3.5.3 结构型模式
结构型模式关注"怎么把类/对象组合成更大的结构"——通过组合获得更强大的功能。
外观模式(Facade)
定义:为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个高层接口,让子系统更容易使用。
为什么用外观模式:
- 子系统很复杂,使用者需要了解很多细节
- 提供一个简单的入口,封装内部的复杂性
- 解耦使用者和子系统,子系统变化不影响使用者
项目中的应用:
- 所有的 Service 层都是外观模式的体现
- StorageService 封装了 Preferences 的复杂操作
- ApiService 封装了网络请求、数据转换、错误处理
- MusicService 封装了播放器的复杂状态管理
代码示例(StorageService 作为外观):
// 复杂的子系统:Preferences API
// 需要:获取实例、put、flush、错误处理...
// 外观层:StorageService
// 对外提供简单的业务方法,内部封装复杂的 Preferences 操作
export class StorageService {
private preferences: preferences.Preferences | null = null;
// 对外:简单的保存收藏方法
async saveFavoriteEthnic(ethnicId: string): Promise<void> {
// 内部:复杂的操作
// 1. 读取现有的收藏列表
const favorites = await this.getFavoriteEthnics();
// 2. 判断是否已收藏
if (!favorites.includes(ethnicId)) {
favorites.push(ethnicId);
}
// 3. 序列化为 JSON
const jsonStr = JSON.stringify(favorites);
// 4. 写入 Preferences
await this.preferences?.put(KEY_FAVORITE_ETHNICS, jsonStr);
// 5. 刷盘
await this.preferences?.flush();
// 6. 错误处理
// ...
}
// 对外:简单的读取收藏方法
async getFavoriteEthnics(): Promise<string[]> {
// 内部:复杂的读取、解析、容错
// ...
}
}
// 使用方:页面
// 只需要调用简单的方法,完全不知道内部是 Preferences 还是数据库
const storage = StorageService.getInstance();
await storage.saveFavoriteEthnic('han');
const favorites = await storage.getFavoriteEthnics();
适用场景:
- 子系统复杂,需要一个简单的入口
- 需要分层设计,每层之间通过外观通信
- 希望封装内部实现,对外只暴露必要的接口
- 新旧系统兼容,用外观做兼容层
💡 外观模式的价值:
外观模式的核心是——封装复杂性,提供简单接口。
使用者不需要知道 StorageService 内部是用 Preferences 还是 SQLite,也不需要知道数据是怎么存的、怎么读的。
只需要知道"调用这个方法就能保存收藏"就够了。
这就是"最少知识原则"——你知道的越少,耦合越低,越容易维护。
适配器模式(Adapter)
定义:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
别名:包装器(Wrapper)
为什么用适配器模式:
- 两个接口不兼容,但需要一起工作
- 不想修改原有代码(因为原有代码是稳定的、经过测试的)
- 隔离外部变化,保护内部系统
项目中的应用:
- ApiService 的数据映射层(防腐层)
- 把外部 API 的数据结构转换成内部的模型
- 第三方 SDK 的封装适配
代码示例(ApiService 防腐层):
// 外部 API 的数据结构(可能会变)
interface EthnicApiItem {
id: string;
name_cn: string; // 注意:字段名和内部不一样
name_en: string;
pop_num: string; // 人口数,字段名不一样
pop_rank: number; // 人口排名,字段名不一样
// ... 其他字段,命名风格、结构都可能不一样
}
// 内部业务模型(稳定的,不能轻易改)
interface EthnicGroup {
id: string;
name: string; // 内部用 name
nameEn: string; // 内部用 nameEn
population: string; // 内部用 population
populationRank: number;// 内部用 populationRank
// ... 其他字段
}
// 适配器:把外部结构转换成内部结构
export class ApiService {
// 数据映射方法(适配器)
private mapApiItemToModel(item: EthnicApiItem): EthnicGroup {
return {
id: item.id,
name: item.name_cn, // 字段名转换
nameEn: item.name_en, // 字段名转换
population: item.pop_num, // 字段名转换
populationRank: item.pop_rank, // 字段名转换
// ... 其他字段的转换、校验、默认值处理
};
}
// 对外:返回内部稳定的模型
async getEthnicById(id: string): Promise<EthnicGroup | null> {
const response = await this.httpClient.get<EthnicApiItem>(`/ethnics/${id}`);
if (response.success && response.data) {
return this.mapApiItemToModel(response.data); // 适配转换
}
return null;
}
}
适用场景:
- 接口不兼容,需要把一个接口转换成另一个
- 使用第三方库/SDK,但不想让业务代码直接依赖它
- 新旧系统对接,数据结构不一样
- 防腐层(隔离外部系统变化)
💡 适配器模式的价值:
适配器模式的核心是——隔离变化,保护内部。
外部 API 的字段名可能会变、结构可能会调整,但我们的业务代码不应该跟着变。
加一层适配器(防腐层),外部的变化被限制在适配器里,业务代码稳定不变。
这就是"依赖倒置原则"的体现——业务代码依赖稳定的内部模型,不依赖易变的外部接口。
3.5.4 设计模式总结
「民族图鉴」6 大设计模式一览:
| 类型 | 模式 | 应用场景 | 核心思想 |
|---|---|---|---|
| 创建型 | 单例模式 | 7 大核心服务 | 全局唯一实例,统一管理 |
| 行为型 | 监听者模式 | MusicService、AppStorage | 一对多依赖,状态变化自动通知 |
| 行为型 | 策略模式 | ApiService(Mock/Remote)、TTS | 多种实现,统一接口,可互换 |
| 行为型 | 状态模式 | TTS 状态机、播放状态 | 状态决定行为,状态变化行为变化 |
| 结构型 | 外观模式 | 所有 Service 层 | 封装复杂底层,提供简单接口 |
| 结构型 | 适配器模式 | ApiService 数据映射(防腐层) | 把不兼容的接口转成期望的接口 |
🎯 设计模式的真谛:
设计模式不是"模板",不是"必须套进去"的东西。它是"前人总结的、针对某类问题的成熟解决方案"。
当你遇到类似问题时,可以参考这些模式,但不要为了用模式而用模式。
好的代码是"刚好解决了问题,并且恰好符合某个模式",而不是"为了符合模式,把代码绕来绕去"。
比模式更重要的是模式背后的原则——单一职责、开闭原则、依赖倒置、最少知识…
这些原则是"道",模式是"术"。道术结合,才能写出好代码。
五、「民族图鉴」v1.0 完整功能清单
说了这么多架构和原则,让我们回到项目本身。「民族图鉴」v1.0 到底有哪些功能?
4.1 核心功能模块
模块一:民族浏览
这是最核心的功能——让用户了解 56 个民族。
| 功能点 | 说明 | 优先级 |
|---|---|---|
| 民族列表 | 56 个民族的列表展示,支持搜索 | P0 |
| 民族详情 | 每个民族的详细介绍页 | P0 |
| 民族服饰 | 服饰图片、服饰介绍 | P1 |
| 民族节日 | 节日介绍、节日时间 | P1 |
| 民族美食 | 特色美食介绍 | P1 |
| 民族建筑 | 建筑特色、图片展示 | P2 |
模块二:文化体验
让用户"沉浸式"体验民族文化。
| 功能点 | 说明 | 优先级 |
|---|---|---|
| TTS 朗读 | 民族介绍文字转语音朗读 | P1 |
| 民族音乐 | 各民族特色音乐播放 | P1 |
| AI 问答 | 和 AI 聊民族文化 | P2 |
| 知识测验 | 民族知识小测试 | P2 |
模块三:用户系统
| 功能点 | 说明 | 优先级 |
|---|---|---|
| 收藏功能 | 收藏喜欢的民族 | P0 |
| 浏览历史 | 看过哪些民族 | P2 |
| 搜索历史 | 搜索过的关键词 | P2 |
模块四:个性化
| 功能点 | 说明 | 优先级 |
|---|---|---|
| 主题切换 | 浅色/深色模式 | P1 |
| 多语言 | 中文/英文双语 | P1 |
| 字体大小 | 大/中/小三档 | P2 |
4.2 页面清单
v1.0 一共 12 个核心页面:
| 页面 | 路径 | 核心功能 |
|---|---|---|
| 启动页 | SplashPage | 品牌展示、初始化 |
| 引导页 | GuidePage | 新手引导、功能介绍 |
| 首页 | IndexPage | 民族分类、推荐、入口 |
| 民族列表页 | EthnicListPage | 56 民族列表、搜索、筛选 |
| 民族详情页 | EthnicDetailPage | 民族详细介绍、服饰、节日 |
| 服饰详情页 | CostumeDetailPage | 服饰高清图、详细介绍 |
| 音乐页 | MusicPage | 民族音乐播放列表 |
| 收藏页 | FavoritePage | 收藏的民族列表 |
| 知识测验页 | QuizPage | 民族知识问答 |
| AI 对话页 | ChatPage | AI 智能问答 |
| 设置页 | SettingPage | 主题、语言、字体设置 |
| 关于页 | AboutPage | 版本信息、关于我们 |
4.3 非功能需求
除了功能,v1.0 还要满足这些非功能需求:
性能指标:
- 冷启动时间 < 2s
- 列表滚动 60FPS
- 平均内存占用 < 200MB
- 包体积 < 50MB
兼容性:
- 支持 HarmonyOS 4.0 及以上
- 适配手机和平板
- 支持折叠屏
质量要求:
- 崩溃率 < 0.1%
- 核心流程测试覆盖率 100%
- 代码评审通过率 100%
4.4 技术栈全景图
「民族图鉴」v1.0 的完整技术栈:
┌─────────────────────────────────────────────────────────────┐
│ 用户界面层 │
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 启动页 │ 首页 │ 列表页 │ 详情页 │ 设置页 │ │
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
│ 20+ 页面 · 50+ 组件 · ArkUI 声明式开发 │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────▼───────────────────────────────┐
│ 服务层 │
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 存储服务 │ TTS │ 音乐服务 │ 主题服务 │ AI 服务 │ │
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
│ 7 大核心服务 · 单例模式 · 监听者模式 · 策略模式 │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────▼───────────────────────────────┐
│ 数据层 │
│ ┌──────────────────────┬───────────────────────────────┐ │
│ │ 数据模型定义 │ Mock 数据 / API 数据 │ │
│ │ 56 民族数据结构 │ 平行字段双语设计 │ │
│ └──────────────────────┴───────────────────────────────┘ │
│ TypeScript 类型安全 · 防腐层 · 数据转换 │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────▼───────────────────────────────┐
│ 基础设施层 │
│ ┌────────┬────────┬────────┬────────┬────────┬─────────┐ │
│ │ 工具函数 │ 常量 │ Http │ 缓存 │ 日志 │ 常量 │ │
│ └────────┴────────┴────────┴────────┴────────┴─────────┘ │
│ 通用能力封装 · 系统能力抽象 · 第三方 SDK 封装 │
└─────────────────────────────────────────────────────────────┘
核心技术清单:
| 分类 | 技术/方案 | 说明 |
|---|---|---|
| 开发语言 | ArkTS | TypeScript 超集,HarmonyOS 首选语言 |
| UI 框架 | ArkUI | 声明式 UI 开发范式 |
| 状态管理 | @State / @Prop / @Link / AppStorage | 框架内置,轻量高效 |
| 持久化 | Preferences | key-value 轻量存储 |
| 网络请求 | @ohos.net.http | 系统原生 HTTP 能力 |
| 多媒体 | AVPlayer / TTS / Image | 系统多媒体能力 |
| 构建工具 | Hvigor | 华为自研构建工具 |
| 开发工具 | DevEco Studio | 官方 IDE |
| 设计模式 | 单例 / 监听者 / 策略 / 外观 / 适配器 / 状态 | 6 大经典模式 |
| 架构模式 | 四层分层架构 | 表现层 / 服务层 / 数据层 / 基础设施层 |
💡 v1.0 的定位:
v1.0 不是"完美的产品",而是"可用的产品"。
它包含最核心的功能,能满足用户最基本的需求——了解 56 个民族。
更多更酷的功能,留给 v2.0、v3.0 去做。小步快跑,快速迭代,比"一步到位"更靠谱。
六、「民族图鉴」技术栈全景
说完了功能,我们再来从技术角度拆解一下「民族图鉴」的完整技术栈。从底层语言到顶层UI,从数据存储到工具链,一层一层来看。
5.1 语言层
语言是一切的基础。「民族图鉴」基于 ArkTS 开发,这是 HarmonyOS 的首选语言。
5.1.1 ArkTS
什么是 ArkTS:
ArkTS 是 TypeScript 的超集,在 TypeScript 的基础上增加了声明式 UI、状态管理等扩展能力。
核心特性:
| 特性 | 说明 |
|---|---|
| 静态类型 | 编译时类型检查,提前发现错误 |
| 声明式UI | 用描述性的方式定义UI,不用命令式操作 |
| 状态管理 | 内置 @State、@Prop、@Link 等装饰器 |
| 自定义组件 | 可组合、可复用的组件体系 |
| 生命周期 | 组件生命周期钩子 |
| 高性能 | 编译优化,运行时性能优秀 |
为什么选 ArkTS:
- 官方首选,生态最好
- 和 ArkUI 深度集成,开发效率最高
- 静态类型,代码质量更高
- 有 TypeScript 基础的开发者上手很快
5.1.2 TypeScript 基础能力
ArkTS 是 TypeScript 的超集,所以 TypeScript 的能力 ArkTS 都有:
- 类型系统:接口、类型别名、泛型、联合类型、交叉类型…
- 面向对象:类、继承、多态、抽象类…
- 函数式:箭头函数、高阶函数、闭包…
- 模块化:ES Module、import/export…
- 高级特性:可选链、空值合并、解构赋值…
「民族图鉴」中的 TypeScript 应用:
- 数据模型定义(interface + 全是类型定义)
- 泛型工具函数
- 枚举类型
- 接口抽象
5.2 UI 层
UI 层是用户直接接触的部分,也是 ArkTS 最有特色的部分。
5.2.1 ArkUI 声明式开发
什么是声明式开发:
命令式开发:"先创建一个按钮,再设置它的文字,再设置点击事件…
声明式开发:“我要一个按钮,文字是xxx,点击时做xxx”
// 声明式:描述UI
Button('点击我')
.fontSize(16)
.onClick(() => {
console.log('点击了');
})
声明式开发的优势:
- 代码更简洁,可读性更高
- 状态和状态绑定是自动的,不用手动更新UI
- 更容易维护
- 更不容易出错
5.2.2 组件体系
ArkUI 提供了丰富的内置组件:
| 分类 | 常用组件 |
|---|---|
| 基础组件 | Text、Image、Button、TextInput、Icon… |
| 容器组件 | Column、Row、Stack、List、Grid、Scroll… |
| 列表组件 | List、ListItem、LazyForEach、Grid、Swiper… |
| 导航组件 | Navigation、Tabs、Router… |
| 表单组件 | TextInput、Slider、Switch、Checkbox、Radio… |
| 媒体组件 | Image、Video、Canvas… |
| 动画组件 | 属性动画、转场动画、动画曲线… |
「民族图鉴」中的组件应用:
- 列表页:List + LazyForEach + 搜索栏
- 详情页:Scroll + Column + 多个卡片组件
- 音乐页:自定义播放控件 + 进度条 + 播放列表
- 设置页:List + 各种设置项组件
5.2.3 自定义组件与@Builder @Extend
除了内置组件,还可以用自定义组件:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 自定义组件 | @Component 装饰的组件 | 独立的、有状态的组件 |
| @Builder | 轻量的UI封装 | 同组件内的 UI 片段复用 |
| @Extend | 扩展原生组件的样式 | 统一样式封装 |
5.3 状态管理层
状态管理是 UI 开发的核心——数据驱动 UI,UI 反映状态。
5.3.1 组件内状态
| 装饰器 | 作用 | 适用场景 |
|---|---|---|
| @State | 组件内部的状态 | 组件自己用,不对外 |
| @Prop | 父传子,单向 | 父组件传给子组件,子组件只读 |
| @Link | 父子双向绑定 | 父子组件双向同步 |
| @Observed / @ObjectLink | 嵌套对象/数组的深层监听 | 复杂对象的状态 |
5.3.2 跨组件状态
| 装饰器 | 作用 | 适用场景 |
|---|---|---|
| @Provide / @Consume | 跨层级、跨组件提供/消费 | 祖先组件提供,后代组件消费 |
| AppStorage | 应用全局存储 | 全局状态,整个App 共享 |
| LocalStorage | 页面级存储 | 页面内共享 |
| PersistentStorage | 持久化存储 | 需要持久化的全局状态 |
5.3.3 Service 单例状态管理
除了框架提供的状态管理,「民族图鉴」还用了 Service 单例 + 监听者模式做状态管理:
- MusicService:播放状态存在 Service 里,通过监听者模式通知UI
- ThemeService:主题状态全局管理
- StorageService:持久化数据 + 内存缓存
为什么用 Service 做状态管理:
- 业务逻辑和 UI 分离
- 状态集中管理,统一
- 可以监听者模式灵活,可多订阅
- 不受组件间共享方便
5.4 服务层
服务层是整个应用的"脊梁",7 大核心服务撑起了整个应用的业务逻辑。
5.4.1 7 大核心服务
| 服务 | 职责 | 核心能力 |
|---|---|---|
| StorageService | 本地持久化存储 | Preferences 封装、收藏/历史/浏览记录 |
| TTSEngineService | 文本转语音 | 朗读、暂停、停止、状态管理 |
| MusicService | 音乐播放 | 播放列表、播放控制、进度管理 |
| ThemeService | 主题切换 | 浅色/深色、字体大小 |
| I18nService | 多语言国际化 | 中/英切换、平行字段 |
| AIService | AI 对话问答 | 对话、上下文管理、Mock 回复 |
| ApiService | 数据 API 封装 | Mock/远程切换、防腐层、降级 |
5.4.2 服务层的设计原则
- 单一职责:每个服务只做一件事
- 单例模式:全局唯一实例
- 监听者模式:状态变化通知 UI
- 外观模式:封装底层,对外提供简单接口
- 错误处理:有降级容错机制
- 统一管理:状态集中管理
5.5 数据层
数据层负责数据的定义、存储和获取。
5.5.1 数据模型
56 个民族数据结构:
- 基本信息:id、名称、拼音、人口、排名…
- 文化信息:语言、文字、宗教、节日…
- 双语设计:中/英文字段平行设计
- 其他:图片、颜色、分布地区…
**模型设计的原则:
- 类型安全:全量 TypeScript 类型定义
- 平行字段:双语字段平行设计
- 结构清晰:层次分明,易于理解
5.5.2 Mock 数据
为什么用 Mock 数据:
- 前后端分离,前端开发不依赖后端
- 开发阶段快速开发
- 演示、测试方便
- 降级方案
Mock 数据的分层:
- 模型层:数据结构定义
- 数据层:Mock 数据生成
- 服务层:对外提供数据接口
5.5.3 Preferences 持久化
轻量级 key-value 存储:
- 简单易用,API 简洁
- 性能好,小数据快
- 支持多种数据类型:string、number、boolean、object…
「民族图鉴」中的存储内容:
- 收藏列表
- 历史记录
- 主题设置
- 语言设置
- 用户偏好
5.6 工具链
工欲善其事,必先利其器。好的工具链能大大提升开发效率。
5.6.1 DevEco Studio
官方 IDE,HarmonyOS 开发的"瑞士军刀":
| 功能 | 说明 |
|---|---|
| 代码编辑 | 智能补全、语法高亮、重构… |
| 可视化预览 | 实时预览 UI 效果 |
| 调试器 | 断点调试、日志查看、性能分析 |
| 性能分析器 | CPU、内存、网络、帧率分析 |
| 布局检查器 | UI 层级、属性查看 |
| 模拟器 | 多种设备模拟器 |
| 一键运行 | 一键安装到设备/模拟器 |
5.6.2 Hvigor 构建工具
华为自研的构建工具:
- 快速构建,增量编译
- 插件化架构,可扩展
- 和 DevEco Studio 深度集成
- 支持多种构建类型(Debug/Release)
5.6.3 Git 版本管理
代码版本管理必备:
- 版本控制,历史记录
- 分支管理,并行开发
- 代码,多人协作
- 代码评审,质量保证
「民族图鉴」的 Git 规范:
- 分支模型:Git Flow / GitHub Flow
- 提交规范:Conventional Commits
- 代码评审:MR/PR 流程
5.7 测试层
质量保证,从测试开始。
5.7.1 单元测试
测什么:
- 工具函数
- Service 层逻辑
- 数据转换逻辑
- 状态管理逻辑
测试框架:
- 单元测试框架
- 断言库
- Mock 工具
「民族图鉴」的单元测试:
- StorageService 测试
- ApiService 测试
- 工具函数测试
- 数据模型测试
5.7.2 集成测试
测什么:
- 页面流程测试
- 组件交互测试
- 服务集成测试
测试内容:
- 页面跳转是否正确
- 列表加载是否正常
- 收藏功能是否正常
- 主题切换是否生效
5.7.3 测试金字塔
/ \ / \
/ \ / E2E \ 少而精
/ \ /-------\
/ 集成 \ / 集成 \ 适中
/ 测试 \ / 测试 \
/-----------\-------------\
/ 单元测试 \ 多而快
/ \
- 单元测试:最多,运行最快,最底层
- 集成测试:适中,中间层
- E2E测试:最少,最上层,最接近真实用户
🎯 技术栈的选择哲学:
技术栈不是越新越好、越复杂越好,而是越合适越好。
「民族图鉴」选择的技术栈,都是 HarmonyOS 开发最主流、最成熟、最适合当前项目规模的方案。
语言选 ArkTS,因为它是官方首选,生态最好;
状态管理用框架内置的 + Service 单例,因为够用了,不用上复杂的状态管理库;
存储用 Preferences,因为数据量不大,key-value 就够了,不用上数据库。适合的,就是最好的。
七、核心最佳实践
让我们回顾一下全系列最重要的 20 条最佳实践。每一条都是踩坑踩出来的经验。
5.1 架构与设计
1. 单一职责原则
一个类、一个函数、一个组件,只做一件事。职责越多,越难维护,越容易出 bug。
2. 面向接口编程
依赖抽象,不依赖具体实现。Mock 和真实 API 对外接口一样,可以无缝切换。
3. 封装变化
把会变的地方封装起来。变化被限制在局部,就不会扩散到整个系统。
4. 单一数据源
全局状态只在一个地方存,所有页面都从这里读。多处存就会多处不一致。
5. 分层架构
表现层、服务层、数据层、基础设施层——上层依赖下层,下层不依赖上层。
3.2 组件与页面
6. 薄页面,厚服务
页面只负责 UI 和用户交互,业务逻辑下沉到服务层。页面越薄越好维护。
7. 三态设计
任何列表/页面都要考虑:加载中、空数据、错误、正常——四种状态缺一不可。
8. Props 向下,事件向上
单向数据流——父传子用 props,子告父用回调。数据流越简单,越容易追踪。
9. 组件三问
抽组件前问自己:复用吗?独立吗?有清晰的边界吗?都满足再抽。
10. 命名即文档
好的命名不需要注释。看名字就知道是干什么的,就是好命名。
3.3 数据与服务
11. 类型先行
先定义数据模型,再写业务逻辑。类型是各层之间沟通的"契约"。
12. 防御性编程
永远不要信任外部输入。网络请求、用户输入、持久化数据——都要校验。
13. 优雅降级
核心功能尽量可用。非核心功能用不了,不要影响核心功能。
14. 防腐层
外部系统的数据结构,不要直接渗透到业务代码里。中间加一层转换,隔离变化。
15. 资源释放
监听器、定时器、事件订阅——注册了就要注销,开了就要关。内存泄漏都是这么来的。
5.4 工程与质量
16. 先测量再优化
不要靠感觉优化。先测,找到瓶颈,再有针对性地优化。2-8法则——80% 的问题集中在 20% 的代码上。
17. 自动化测试
单元测试要多,集成测试适中,E2E 测试要精。测试是重构的底气。
18. 代码评审
CR 不是走流程,是质量保障。对事不对人,既找问题也学习。
19. 调试有方法
复现 → 缩小范围 → 假设验证 → 修复 → 复盘。二分法是神器。
20. 持续改进
架构不是一次设计完就不动了。随着项目成长,架构也要演进。小步快跑,持续改进。
🎯 最佳实践的本质:
这些"最佳实践"不是什么"行业规定",而是无数程序员踩了无数坑之后,总结出来的"能少踩坑的写法"。
它们不是教条,不是必须 100% 遵守。你可以根据项目规模、团队情况、时间紧迫程度,灵活取舍。
但你要知道有这些最佳实践,知道"标准写法"是什么样的——然后根据情况决定哪些严格遵守,哪些可以放松。
八、开发者能力成长路径
看完了 46 篇,接下来该怎么成长?给你一条清晰的成长路径。
6.1 成长的五个阶段
从新手到专家,大概要经历这五个阶段:
阶段一:能写出来(入门级)
特征:
- 能照着教程写出功能
- 遇到问题知道搜,但不知道为什么
- 代码能跑,但自己也不太确定为什么能跑
要做的事:
- 多写代码,量变引起质变
- 把「民族图鉴」项目跟着做一遍
- 遇到问题先自己想,想不出来再搜
- 搞懂每一行代码为什么这么写
目标:能独立完成简单功能的开发。
阶段二:能写好(进阶级)
特征:
- 代码结构清晰,命名规范
- 知道有哪些最佳实践,会照着做
- 能 review 新人的代码,指出明显的问题
要做的事:
- 学习设计模式和架构原则
- 多读好代码(优秀开源项目)
- 多做 code review,看别人怎么写
- 开始关注性能、可维护性
目标:写出清晰、可维护、质量不错的代码。
阶段三:能设计(高级级)
特征:
- 能做模块设计、架构设计
- 能评估技术方案的优劣
- 能考虑到扩展性、可维护性、性能等
- 能带领小团队做项目
要做的事:
- 学习系统的架构知识
- 参与/主导复杂项目的设计
- 多思考"为什么这么设计"
- 了解行业内的优秀实践
目标:能独立设计中等复杂度的系统。
阶段四:能解决难题(专家级)
特征:
- 遇到疑难杂症,别人搞不定的你能搞定
- 能做性能优化、架构重构
- 对技术有深入的理解,知道原理
- 能制定技术规范和最佳实践
要做的事:
- 深入底层原理(编译器、虚拟机、操作系统)
- 解决别人解决不了的问题
- 做技术分享,输出倒逼输入
- 形成自己的技术体系和方法论
目标:成为团队的技术中坚,解决最难的问题。
阶段五:能做决策(架构师级)
特征:
- 能做技术选型,决定用什么技术栈
- 能平衡技术、业务、成本
- 有技术 vision,知道未来的方向
- 能带领团队,培养人
要做的事:
- 拓宽技术视野,不只关注一个领域
- 深入理解业务,技术服务于业务
- 提升软技能:沟通、协作、领导力
- 培养技术敏感度,预判技术趋势
6.2 给你的建议
1. 动手实践,不要光看
- 看完 46 篇只是开始,动手做项目才是真正的学习
- 把「民族图鉴」改造成你自己的版本,加你想加的功能
- 遇到的每一个 bug,都是成长的机会
2. 建立知识体系,不要零散学习
- 不要今天学这个框架,明天学那个库
- 先把基础打牢:数据结构、算法、网络、操作系统
- 建立自己的知识图谱,知道每个知识点在什么位置
3. 输出倒逼输入
- 写技术博客、做分享
- 给别人讲明白,才是真的懂了
- 输出的过程就是梳理和深化的过程
4. 保持长期主义
- 技术成长不是一蹴而就的,是日积月累的
- 每天进步一点点,一年后就是巨大的差距
- 不要焦虑,保持节奏,持续学习
🎯 成长的真相:
没有什么"30天成为高手"的捷径。
真正的成长,是在日复一日的编码中,在一个又一个 bug 的调试中,在一次又一次的架构设计中,慢慢积累起来的。
但只要你保持学习、保持思考、保持实践,你就会越来越强。
九、项目演进路线
现在的民族图鉴,还是一个"MVP"(最小可行产品)阶段的项目。如果要继续做大做强,可以往哪些方向演进?
4.1 第一阶段:完善基础(当前 → v1.5)
当前已经有了核心功能,但还有很多基础工作可以做:
- 完善单元测试:服务层、工具函数的测试覆盖率提到 80%+
- 补全组件库:抽出更多通用组件(按钮、卡片、空状态、加载状态等)
- 错误边界:全局错误捕获,页面崩了有友好提示
- 埋点统计:用户行为埋点,知道用户怎么用产品
- 性能监控:FPS、启动时间、内存使用、崩溃率监控
4.2 第二阶段:真实数据与后端(v1.5 → v2.0)
现在的数据还是 Mock 的。接真实后端,是下一步的重点:
- 后端 API 开发:民族数据、用户系统、收藏同步等接口
- 用户系统:注册、登录、个人中心
- 数据管理后台:运营可以编辑民族数据,不用改代码
- 内容审核:用户生成内容(如果有的话)的审核机制
- CDN 加速:图片、静态资源走 CDN
4.3 第三阶段:功能增强(v2.0 → v3.0)
基础打牢了,可以加更多有意思的功能:
- AR 民族服饰:相机试穿民族服饰
- 语音问答:语音输入,AI 语音回答
- 民族音乐:更多民族音乐,完整的播放器
- 知识测验进阶:排行榜、成就系统、多人对战
- VR 展馆:3D 虚拟民族博物馆
- 社区功能:用户分享、讨论、打卡
4.4 第四阶段:架构演进(v3.0+)
项目变大了,架构也要跟着演进:
- 模块化拆分:按业务域拆成多个模块(民族模块、音乐模块、AI模块…)
- 组件库独立:公共组件抽成单独的库,可以给其他项目用
- 微前端/动态化:支持动态下发页面,不用发版就能更新
- 多端适配:手机、平板、折叠屏、车机、穿戴设备…
- 国际化:不只中英文,支持更多语言
7.5 演进的原则
不管怎么演进,有几个原则要坚持:
- 渐进式演进:不要上来就搞最复杂的架构,先简单,再逐步复杂化
- 保留退路:每一步改动都可以回退,不要"一步到位"式的重构
- 价值驱动:每做一件事,想清楚"为用户/产品带来了什么价值"
- 技术服务于业务:技术是手段,不是目的。不要为了炫技而加技术
💡 架构是演化出来的,不是设计出来的。
好的架构不是一开始就设计好的,而是随着项目成长,一步步演进来的。每一步都解决当时的问题,每一步都为未来留有余地。
不要追求"完美的架构"——没有完美的架构,只有"适合当前阶段"的架构。
十、后续 54 篇内容预告
这是「鸿蒙开发 100 篇」的前 46 篇。后面还有 54 篇,会往哪些方向深入?给你提前剧透一下。
8.1 进阶开发篇(第 47-60 篇,14 篇)
深入 HarmonyOS 高级开发能力:
| 主题 | 内容预告 |
|---|---|
| 自定义组件 | 从零实现一个复杂的自定义组件 |
| 自定义绘制 | Canvas 2D 绘制、自定义绘制组件 |
| 动画进阶 | 属性动画、转场动画、粒子动画、弹簧动画 |
| 手势进阶 | 复杂手势识别、手势冲突处理、手势组合 |
| 状态栏导航栏 | 沉浸式、状态栏适配、安全区域 |
| 折叠屏适配 | 折叠屏特性、分屏、悬停模式 |
| 多端协同 | 分布式能力、跨端迁移、多端协同 |
| 后台任务 | 后台播放、后台下载、长任务处理 |
| 通知与提醒 | 本地通知、推送通知、角标 |
| 桌面小组件 | 服务卡片(Widget)开发 |
| 分享与打开 | 系统分享、文件打开、跨应用跳转 |
| 权限管理 | 权限申请、权限校验、权限最佳实践 |
8.2 多媒体篇(第 61-70 篇,10 篇)
音视频、相机、图片处理:
| 主题 | 内容预告 |
|---|---|
| 图片加载进阶 | 三级缓存、大图加载、图片解码优化 |
| 视频播放 | 视频播放器、全屏播放、手势控制 |
| 音频播放 | 完整音乐播放器、后台播放、音频焦点 |
| 音视频录制 | 录音、录像、屏幕录制 |
| 相机开发 | 拍照、录像、滤镜、美颜 |
| 媒体编辑 | 图片裁剪、滤镜、视频剪辑 |
| 直播推流 | RTMP 推流、直播互动 |
8.3 数据与网络篇(第 71-80 篇,10 篇)
更深入的数据处理和网络能力:
| 主题 | 内容预告 |
|---|---|
| 关系型数据库 | SQLite、ORM、数据迁移 |
| 文件管理 | 文件读写、目录管理、存储策略 |
| 网络编程 | Socket、WebSocket、长连接 |
| 数据同步 | 本地数据与云端同步、冲突处理 |
| 加密与安全 | 数据加密、签名验证、安全存储 |
| 序列化 | JSON/Protocol Buffers、序列化性能 |
| 下载上传 | 断点续传、大文件上传、下载管理 |
8.4 架构与性能篇(第 81-90 篇,10 篇)
更高阶的架构设计和性能优化:
| 主题 | 内容预告 |
|---|---|
| 响应式架构 | 响应式编程、状态管理进阶 |
| 领域驱动设计 | DDD 在客户端的应用 |
| 模块化架构 | 模块拆分、模块通信、模块路由 |
| 组件库建设 | 企业级组件库设计与实现 |
| 性能监控 | 性能埋点、APM、线上性能分析 |
| 启动优化深入 | 启动流程源码级分析、极致优化 |
| 内存优化深入 | 内存模型、GC 原理、内存泄漏检测 |
| 包体积优化 | 代码缩减、资源优化、动态下发 |
8.5 实战与进阶篇(第 91-100 篇,10 篇)
综合实战和职业发展:
| 主题 | 内容预告 |
|---|---|
| 完整项目实战 | 从零到一实现一个完整的商业级 App |
| 应用上架 | 应用市场上架流程、审核要点 |
| 运营与增长 | 埋点分析、用户增长、A/B 测试 |
| 团队协作 | Git 工作流、代码规范、代码评审 |
| 技术选型 | 技术选型方法论、技术债管理 |
| 技术领导力 | 技术方案设计、技术分享、带人成长 |
| 职业发展 | 程序员的职业规划、成长路径 |
🎯 100 篇的意义:
前 46 篇是"基础"——让你能做出一个完整的 App。
中间 44 篇是"进阶"——让你能做出高质量、高性能的 App。
最后 10 篇是"升华"——让你从"会写代码"到"会做项目"再到"能带团队"。
路漫漫其修远兮,我们一起上下求索。
十一、写在最后
46 篇技术文章,从基础语法到页面开发,从数据服务到架构工程,我们一起走完了一个 HarmonyOS 应用的完整技术栈。
希望这个系列能给你带来一些启发——
- 也许是某个设计模式,让你恍然大悟"原来还可以这么写"
- 也许是某个最佳实践,让你想到了项目里可以改进的地方
- 也许是架构设计的思路,让你对整体设计有了更高一层的理解
- 也许是调试排错的方法,让你下次遇到 bug 不再慌
技术的世界很大,我们永远在学习的路上。
愿你写出优雅的代码,做出好用的产品,成为更好的工程师。
我们在后续的 54 篇里,再见。
—— 未完待续 ——
更多推荐



所有评论(0)