及时做APP开发实战(三)-数据模型设计好的数据模型设计是应用成功的基石。本文以及时做应用为例详细讲解如何从番茄工作法需求出发设计出合理的数据模型。一、需求分析1.1 番茄工作法简介番茄工作法是一种时间管理方法核心流程如下┌─────────────────────────────────────┐ │ 选择一个待完成的任务 │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ 设定25分钟倒计时一个番茄钟 │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ 专注工作直到计时器响起 │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ 短暂休息5分钟 │ └──────────────┬──────────────────────┘ │ 每4个番茄钟后 长休息15分钟【图1番茄工作法流程图】1.2 功能需求拆解基于番茄工作法及时做应用需要以下核心功能待办管理创建、编辑、删除待办任务标记任务完成状态查看待办列表番茄钟计时25分钟倒计时开始、暂停、重置休息提醒数据统计今日完成任务数今日专注时长历史记录查看二、数据模型设计2.1 实体识别从需求中识别出两个核心实体待办任务TodoItem用户要完成的事项番茄钟记录PomodoroRecord每次专注的记录2.2 TodoItem模型设计/** * 待办任务数据模型 */exportinterfaceTodoItem{/** 唯一标识使用时间戳生成 */id:number;/** 任务文本内容 */text:string;/** 完成状态 */completed:boolean;/** 创建时间戳 */createdAt:number;/** 番茄钟完成次数 */pomodoroCount:number;}字段设计理由字段类型说明idnumber使用时间戳作为唯一标识简单高效textstring任务描述限制200字符completedboolean完成状态用于筛选和统计createdAtnumber创建时间用于排序和历史查询pomodoroCountnumber番茄钟次数显示专注程度为什么不存储番茄钟记录列表避免数据冗余番茄钟记录独立存储通过todoId关联统计时再聚合查询2.3 PomodoroRecord模型设计/** * 番茄钟记录数据模型 */exportinterfacePomodoroRecord{/** 唯一标识 */id:number;/** 关联的待办任务ID */todoId:number;/** 单次专注时长分钟 */duration:number;/** 完成时间戳 */completedAt:number;}字段设计理由字段类型说明idnumber记录唯一标识todoIdnumber外键关联TodoItemdurationnumber实际专注时长支持自定义completedAtnumber完成时间用于统计和历史查询为什么记录duration而不是使用固定值用户可能调整番茄钟时长支持自定义专注时长统计数据更准确三、工厂函数设计3.1 为什么需要工厂函数直接使用对象字面量创建对象存在风险容易遗漏字段默认值不统一类型检查不严格工厂函数解决了这些问题/** * 创建新的待办任务 */exportfunctioncreateTodoItem(text:string):TodoItem{return{id:Date.now(),text:text,completed:false,createdAt:Date.now(),pomodoroCount:0};}/** * 创建新的番茄钟记录 */exportfunctioncreatePomodoroRecord(todoId:number,duration:number):PomodoroRecord{return{id:Date.now(),todoId:todoId,duration:duration,completedAt:Date.now()};}3.2 工厂函数的优势// 不推荐直接创建对象consttodo:TodoItem{id:Date.now(),text:学习ArkTS,completed:false,createdAt:Date.now(),pomodoroCount:0};// 推荐使用工厂函数consttodocreateTodoItem(学习ArkTS);优势对比代码简洁一行代码完成创建默认值统一所有字段自动填充类型安全TypeScript自动推断类型易于维护修改默认值只需改一处四、业务逻辑实现4.1 TodoViewModel设计exportclassTodoViewModel{privatetodoList:TodoItem[][];/** * 添加新待办 */asyncaddTodo(text:string):PromiseTodoItem{consttodocreateTodoItem(text);this.todoList.push(todo);awaitthis.preferencesHelper.addTodo(todo);returntodo;}/** * 切换完成状态 */asynctoggleTodoComplete(todoId:number):Promisevoid{consttodothis.todoList.find(itemitem.idtodoId);if(todo){todo.completed!todo.completed;awaitthis.updateTodo(todo);}}/** * 增加番茄钟计数 */asyncincrementPomodoroCount(todoId:number):Promisevoid{consttodothis.todoList.find(itemitem.idtodoId);if(todo){todo.pomodoroCount;awaitthis.updateTodo(todo);}}}4.2 PomodoroViewModel设计exportclassPomodoroViewModel{privaterecords:PomodoroRecord[][];// 番茄钟配置常量privatestaticreadonlyDEFAULT_DURATION25;privatestaticreadonlySHORT_BREAK5;privatestaticreadonlyLONG_BREAK15;/** * 完成一次番茄钟 */asynccompletePomodoro(todoId:number,duration:numberPomodoroViewModel.DEFAULT_DURATION):PromisePomodoroRecord{// 1. 创建记录constrecordcreatePomodoroRecord(todoId,duration);this.records.push(record);// 2. 持久化存储awaitthis.preferencesHelper.addPomodoroRecord(record);// 3. 更新待办的番茄钟计数awaitthis.todoViewModel.incrementPomodoroCount(todoId);returnrecord;}}五、统计功能实现5.1 今日统计/** * 获取今日番茄钟数量 */getTodayPomodoroCount():number{consttodayStartthis.getTodayStartTimestamp();returnthis.records.filter(recordrecord.completedAttodayStart).length;}/** * 获取今日专注总时长分钟 */getTodayTotalDuration():number{consttodayStartthis.getTodayStartTimestamp();returnthis.records.filter(recordrecord.completedAttodayStart).reduce((total,record)totalrecord.duration,0);}/** * 获取今日开始时间戳 */privategetTodayStartTimestamp():number{consttodaynewDate();today.setHours(0,0,0,0);returntoday.getTime();}5.2 待办统计/** * 获取未完成的待办数量 */getActiveTodoCount():number{returnthis.todoList.filter(item!item.completed).length;}/** * 获取已完成的待办数量 */getCompletedTodoCount():number{returnthis.todoList.filter(itemitem.completed).length;}六、数据关联查询6.1 查询待办的所有番茄钟记录/** * 获取指定待办的番茄钟记录 */asyncgetPomodoroRecordsByTodoId(todoId:number):PromisePomodoroRecord[]{constrecordsawaitthis.getPomodoroRecords();returnrecords.filter(recordrecord.todoIdtodoId);}6.2 使用示例// 查询某个待办的专注历史consttodoId1234567890;constrecordsawaitpreferencesHelper.getPomodoroRecordsByTodoId(todoId);// 计算总专注时长consttotalDurationrecords.reduce((sum,r)sumr.duration,0);console.log(总专注时长:${totalDuration}分钟);七、数据验证7.1 输入验证exportclassValidateUtil{/** * 验证待办文本是否有效 */staticisValidTodoText(text:string):boolean{returntext.trim().length0text.trim().length200;}/** * 清理待办文本 */staticsanitizeTodoText(text:string):string{returntext.trim().substring(0,200);}}7.2 使用验证asyncaddTodo(text:string):PromiseTodoItem|null{// 验证输入if(!ValidateUtil.isValidTodoText(text)){console.warn(Invalid todo text);returnnull;}// 清理输入constcleanTextValidateUtil.sanitizeTodoText(text);// 创建待办consttodocreateTodoItem(cleanText);// ...}八、最佳实践总结8.1 数据模型设计原则单一职责每个模型只描述一类实体字段最小化只存储必要字段避免冗余类型安全使用TypeScript类型注解语义清晰字段命名要见名知意8.2 关联关系处理使用外键通过ID关联不嵌套对象独立存储关联数据分开存储按需查询需要时再聚合查询8.3 工厂函数使用统一创建入口所有对象创建通过工厂函数默认值集中管理便于维护和修改类型自动推断减少类型错误8.4 统计功能设计时间范围过滤使用时间戳过滤聚合计算使用reduce等方法聚合缓存优化频繁计算的统计数据可缓存九、扩展性考虑当前设计为后续扩展预留了空间自定义时长duration字段支持自定义历史查询completedAt字段支持时间范围查询标签分类可在TodoItem中添加tags字段优先级可在TodoItem中添加priority字段提醒时间可在TodoItem中添加reminderAt字段这种设计遵循了开闭原则对扩展开放对修改封闭。