第12次:教程数据服务层
服务层是应用架构中的关键一环,负责封装业务逻辑和数据访问。本次课程将学习如何设计和实现 TutorialService,为应用提供统一的数据访问接口。
学习目标
- 理解服务层的设计模式
- 掌握静态类的使用方法
- 学会数据缓存策略
- 实现服务初始化流程
- 完成 TutorialService 的完整开发
12.1 服务层设计模式
什么是服务层?
服务层(Service Layer)是一种架构模式,位于 UI 层和数据层之间:
┌─────────────────────────────────────┐
│ UI 层 (Pages) │
├─────────────────────────────────────┤
│ 服务层 (Services) │ ← 本次课程重点
├─────────────────────────────────────┤
│ 数据层 (Data) │
└─────────────────────────────────────┘
服务层的职责
| 职责 | 说明 |
|---|---|
| 数据访问封装 | 统一数据获取接口 |
| 业务逻辑处理 | 实现业务规则 |
| 数据转换 | 格式化和处理数据 |
| 缓存管理 | 提高数据访问效率 |
| 错误处理 | 统一异常处理 |
为什么需要服务层?
- 解耦:UI 层不直接依赖数据层
- 复用:多个页面可共享同一服务
- 测试:便于单元测试
- 维护:修改数据源只需改服务层
12.2 静态类设计
静态类 vs 实例类
在 ArkTS 中,服务层通常使用静态类:
typescript
// 静态类:通过类名直接调用
export class TutorialService {
static getAllModules(): LearningModule[] {
// ...
}
}
// 使用
const modules = TutorialService.getAllModules();
typescript
// 实例类:需要创建实例
export class TutorialService {
getAllModules(): LearningModule[] {
// ...
}
}
// 使用
const service = new TutorialService();
const modules = service.getAllModules();
静态类的优势
- 无需实例化:直接调用,使用简单
- 全局单例:天然的单例模式
- 内存效率:不创建多余对象
静态属性与方法
typescript
export class TutorialService {
// 静态属性
private static initialized: boolean = false;
private static cachedModules: LearningModule[] = [];
// 静态方法
static init(): void {
// 初始化逻辑
}
static getAllModules(): LearningModule[] {
// 获取数据逻辑
}
}
12.3 数据缓存策略
为什么需要缓存?
每次调用 TutorialData.getAllModules() 都会重新构建数据,影响性能。通过缓存可以:
- 减少重复计算
- 提高响应速度
- 降低内存分配
缓存实现
typescript
export class TutorialService {
private static initialized: boolean = false;
private static cachedModules: LearningModule[] = [];
static getAllModules(): LearningModule[] {
// 如果未初始化,先初始化
if (!TutorialService.initialized) {
TutorialService.init();
}
// 返回缓存数据
return TutorialService.cachedModules;
}
static init(): void {
if (TutorialService.initialized) {
return; // 避免重复初始化
}
// 加载数据到缓存
TutorialService.cachedModules = TutorialData.getAllModules();
TutorialService.initialized = true;
}
}
缓存失效策略
在某些场景下需要刷新缓存:
typescript
static refreshCache(): void {
TutorialService.cachedModules = TutorialData.getAllModules();
console.info('[TutorialService] Cache refreshed');
}
12.4 服务初始化流程
初始化时机
服务初始化通常在应用启动时进行:
typescript
// Index.ets
aboutToAppear(): void {
// 初始化主题
initTheme(getContext(this));
// 初始化教程服务
TutorialService.init();
// 加载数据
this.loadData();
}
初始化依赖
TutorialService 初始化时会级联初始化其他服务:
typescript
static init(): void {
if (TutorialService.initialized) {
return;
}
// 1. 加载模块数据
TutorialService.cachedModules = TutorialData.getAllModules();
// 2. 初始化搜索服务(依赖模块数据)
SearchService.init(TutorialService.cachedModules);
// 3. 初始化测验服务
QuizService.init(TutorialData.getAllQuizzes());
// 4. 初始化徽章服务
BadgeService.init(TutorialService.cachedModules);
TutorialService.initialized = true;
console.info('[TutorialService] Initialized');
}
初始化顺序
TutorialService.init()
│
├── 加载 TutorialData
│
├── SearchService.init()
│
├── QuizService.init()
│
└── BadgeService.init()
12.5 数据访问方法
获取所有模块
typescript
static getAllModules(): LearningModule[] {
if (!TutorialService.initialized) {
TutorialService.init();
}
return TutorialService.cachedModules;
}
根据 ID 获取模块
typescript
static getModuleById(id: string): LearningModule | undefined {
return TutorialService.getAllModules().find(m => m.id === id);
}
根据 ID 获取课程
typescript
static getLessonById(moduleId: string, lessonId: string): Lesson | undefined {
const module = TutorialService.getModuleById(moduleId);
return module?.lessons.find(l => l.id === lessonId);
}
按难度筛选模块
typescript
static getModulesByDifficulty(difficulty: DifficultyType): LearningModule[] {
return TutorialService.getAllModules().filter(m => m.difficulty === difficulty);
}
统计方法
typescript
// 获取总课程数
static getTotalLessonCount(): number {
return TutorialService.getAllModules().reduce(
(total, module) => total + module.lessons.length,
0
);
}
// 获取总模块数
static getTotalModuleCount(): number {
return TutorialService.getAllModules().length;
}
// 按难度统计模块数
static getModuleCountByDifficulty(): Map<DifficultyType, number> {
const counts = new Map<DifficultyType, number>();
const difficulties: DifficultyType[] = ['beginner', 'basic', 'intermediate', 'advanced', 'ecosystem'];
for (const diff of difficulties) {
counts.set(diff, TutorialService.getModulesByDifficulty(diff).length);
}
return counts;
}
12.6 实操:实现 TutorialService.ets
步骤一:创建服务文件
在 entry/src/main/ets/services/ 目录下创建 TutorialService.ets 文件。
步骤二:编写完整服务代码
typescript
/**
* 教程数据服务
* 封装 TutorialData 的访问方法
*/
import { TutorialData } from '../data/TutorialData';
import { LearningModule, Lesson, Quiz, DailyQuestion, DifficultyType } from '../models/Models';
import { SearchService } from './SearchService';
import { QuizService } from './QuizService';
import { BadgeService } from './BadgeService';
/**
* 教程服务类
*/
export class TutorialService {
// 初始化标志
private static initialized: boolean = false;
// 缓存的模块数据
private static cachedModules: LearningModule[] = [];
/**
* 初始化服务
* 加载数据并初始化依赖服务
*/
static init(): void {
if (TutorialService.initialized) {
return;
}
// 加载所有模块到缓存
TutorialService.cachedModules = TutorialData.getAllModules();
// 初始化搜索服务
SearchService.init(TutorialService.cachedModules);
// 初始化测验服务
QuizService.init(TutorialData.getAllQuizzes());
// 初始化徽章服务
BadgeService.init(TutorialService.cachedModules);
TutorialService.initialized = true;
console.info('[TutorialService] Initialized with', TutorialService.cachedModules.length, 'modules');
}
/**
* 获取所有模块
* @returns 所有学习模块数组
*/
static getAllModules(): LearningModule[] {
if (!TutorialService.initialized) {
TutorialService.init();
}
return TutorialService.cachedModules;
}
/**
* 根据 ID 获取模块
* @param id 模块 ID
* @returns 模块对象或 undefined
*/
static getModuleById(id: string): LearningModule | undefined {
return TutorialService.getAllModules().find(m => m.id === id);
}
/**
* 根据 ID 获取课程
* @param moduleId 模块 ID
* @param lessonId 课程 ID
* @returns 课程对象或 undefined
*/
static getLessonById(moduleId: string, lessonId: string): Lesson | undefined {
const module = TutorialService.getModuleById(moduleId);
return module?.lessons.find(l => l.id === lessonId);
}
/**
* 按难度获取模块
* @param difficulty 难度等级
* @returns 指定难度的模块数组
*/
static getModulesByDifficulty(difficulty: DifficultyType): LearningModule[] {
return TutorialService.getAllModules().filter(m => m.difficulty === difficulty);
}
/**
* 获取每日一题
* @returns 每日一题对象
*/
static getDailyQuestion(): DailyQuestion {
return TutorialData.getDailyQuestion();
}
/**
* 获取课程测验
* @param lessonId 课程 ID
* @returns 测验对象或 undefined
*/
static getQuizForLesson(lessonId: string): Quiz | undefined {
return TutorialData.getQuizForLesson(lessonId);
}
/**
* 获取所有测验
* @returns 所有测验数组
*/
static getAllQuizzes(): Quiz[] {
return TutorialData.getAllQuizzes();
}
/**
* 获取总课程数
* @returns 所有模块的课程总数
*/
static getTotalLessonCount(): number {
return TutorialService.getAllModules().reduce(
(total, module) => total + module.lessons.length,
0
);
}
/**
* 获取总模块数
* @returns 模块总数
*/
static getTotalModuleCount(): number {
return TutorialService.getAllModules().length;
}
/**
* 获取各难度级别的模块数
* @returns 难度到模块数的映射
*/
static getModuleCountByDifficulty(): Map<DifficultyType, number> {
const counts = new Map<DifficultyType, number>();
const difficulties: DifficultyType[] = ['beginner', 'basic', 'intermediate', 'advanced', 'ecosystem'];
for (const diff of difficulties) {
counts.set(diff, TutorialService.getModulesByDifficulty(diff).length);
}
return counts;
}
}
步骤三:在页面中使用服务
更新 Index.ets,使用 TutorialService:
typescript
import { TutorialService } from '../services/TutorialService';
import { LearningModule } from '../models/Models';
@Entry
@Component
struct Index {
@State modules: LearningModule[] = [];
@State isLoading: boolean = true;
aboutToAppear(): void {
this.initAndLoadData();
}
private async initAndLoadData(): Promise<void> {
try {
// 初始化存储
await StorageUtil.init(getContext(this));
// 初始化主题
initTheme(getContext(this));
// 初始化教程服务并获取数据
TutorialService.init();
this.modules = TutorialService.getAllModules();
console.info('[Index] Loaded', this.modules.length, 'modules');
} catch (error) {
console.error('[Index] Failed to load data:', error);
} finally {
this.isLoading = false;
}
}
build() {
// UI 代码...
}
}
步骤四:使用服务获取数据
typescript
// 获取所有模块
const allModules = TutorialService.getAllModules();
// 获取入门模块
const beginnerModules = TutorialService.getModulesByDifficulty('beginner');
// 获取特定模块
const module = TutorialService.getModuleById('beginner-01');
// 获取特定课程
const lesson = TutorialService.getLessonById('beginner-01', 'b01-l01');
// 获取统计数据
const totalLessons = TutorialService.getTotalLessonCount();
const totalModules = TutorialService.getTotalModuleCount();
// 获取每日一题
const dailyQuestion = TutorialService.getDailyQuestion();
服务层最佳实践
1. 单一职责
每个服务只负责一个领域:
typescript
TutorialService // 教程数据
ProgressService // 学习进度
QuizService // 测验功能
BookmarkService // 收藏功能
2. 错误处理
在服务层统一处理错误:
typescript
static getModuleById(id: string): LearningModule | undefined {
try {
return TutorialService.getAllModules().find(m => m.id === id);
} catch (error) {
console.error('[TutorialService] getModuleById error:', error);
return undefined;
}
}
3. 日志记录
添加适当的日志便于调试:
typescript
static init(): void {
console.info('[TutorialService] Initializing...');
// ...
console.info('[TutorialService] Initialized with', count, 'modules');
}
4. 类型安全
使用 TypeScript 类型确保类型安全:
typescript
static getModulesByDifficulty(difficulty: DifficultyType): LearningModule[] {
// DifficultyType 限制了参数只能是有效的难度值
}
本次课程小结
通过本次课程,你已经:
✅ 理解了服务层的设计模式
✅ 掌握了静态类的使用方法
✅ 学会了数据缓存策略
✅ 实现了服务初始化流程
✅ 完成了 TutorialService 的完整开发
课后练习
-
添加搜索方法:实现根据关键词搜索模块和课程
-
添加排序方法:实现按难度、课时数等排序
-
添加分页方法:实现模块列表分页获取
下次预告
第13次:教程数据内容编写
我们将学习如何组织教程内容数据:
- 入门模块内容设计
- 基础模块内容设计
- 进阶模块内容设计
- 数据结构最佳实践
开始填充真实的教程内容!