# Flutter 数据模型实践与技巧 ## 一、已完成的数据模型 ### 1. Book(书籍模型)✅ **核心功能:** - 电子书基本信息管理(书名、作者、文件格式等) - 阅读状态跟踪(阅读中、已完成、待阅读) - 文件信息存储(路径、大小、格式) - 扩展信息支持(标签、评分、简介) **设计亮点:** - 使用`BookFormat`和`ReadingStatus`枚举确保类型安全 - 提供`Book.create()`工厂方法简化对象创建 - 完整的序列化支持(toMap/fromMap) - 不可变对象设计,所有字段使用final ### 2. Highlight(高亮模型)✅ **核心功能:** - 文本高亮记录(选中文本、位置范围) - 多颜色分类支持(5种颜色对应不同用途) - 章节关联(chapterId可选) - 时间戳记录 **设计亮点:** - `HighlightColor`枚举提供语义化颜色分类 - `Highlight.create()`工厂方法提供默认颜色 - `textLength`计算属性获取高亮长度 - `isInRange()`方法检查高亮范围 ## 二、Flutter数据模型设计最佳实践 ### 1. 不可变对象模式 ```dart class Book { final String title; // 所有字段都是final // 通过copyWith创建新对象,而不是修改原对象 Book copyWith({String? title}) { return Book(title: title ?? this.title); } } ``` **优势:** - 🔒 线程安全 - 🔄 状态管理友好(Flutter状态管理依赖对象比较) - 🐛 减少意外修改bug ### 2. 空值安全设计 ```dart final String title; // 必需字段 final String? author; // 可为null的字段 final List tags = const []; // 默认空列表,避免null ``` **关键符号:** - `final`:字段只能在构造函数中赋值 - `?`:字段可以为null - `required`:构造函数必需参数 - `const []`:不可变空列表 ### 3. 枚举的正确使用 ```dart enum BookFormat { epub, mobi, txt, pdf } // 存储时:枚举 → 字符串 'format': format.name, // "epub" // 读取时:字符串 → 枚举 format: BookFormat.values.firstWhere((e) => e.name == "epub") ``` ### 4. 工厂构造函数模式 ```dart // 便利构造函数 factory Book.create({ required String title, required String filePath, // 自动生成字段 }) { return Book( id: _generateId(), // 自动生成ID addedDate: DateTime.now(), // 自动设置时间 status: ReadingStatus.pending, // 默认状态 title: title, filePath: filePath, // ... ); } ``` **设计原则:** - 自动生成ID和时间戳 - 为常用参数提供默认值 - 封装复杂的创建逻辑 ### 5. 完整的对象生命周期管理 ```dart class Book { // 构造函数 const Book({required this.title}); // 创建便利方法 factory Book.create({...}) { ... } // 修改方法 Book copyWith({...}) { ... } // 序列化方法 Map toMap() { ... } factory Book.fromMap(Map map) { ... } // 调试方法 String toString() { ... } // 比较方法 bool operator ==(Object other) { ... } int get hashCode => ...; } ``` ## 三、常见陷阱与解决方案 ### 1. 枚举序列化错误 ```dart // ❌ 错误:直接存储枚举对象 'format': format, // 会导致序列化失败 // ✅ 正确:转换为字符串 'format': format.name, // 读取时反向转换 format: BookFormat.values.firstWhere((e) => e.name == map['format']) ``` ### 2. copyWith方法遗漏字段 ```dart // ❌ 错误:遗漏了chapterId参数 Highlight copyWith({String? bookId, ...}) { return Highlight( bookId: bookId ?? this.bookId, // 缺少 chapterId 参数 ); } // ✅ 正确:包含所有字段 Highlight copyWith({String? bookId, String? chapterId, ...}) { return Highlight( bookId: bookId ?? this.bookId, chapterId: chapterId ?? this.chapterId, // 确保完整性 ); } ``` ### 3. 工厂方法参数设计错误 ```dart // ❌ 错误:要求用户传入自动生成的字段 factory Highlight.create({ required String id, // 用户不应该手动传入ID required DateTime createdTime, // 应该自动生成 // ... }); // ✅ 正确:只要求用户必须提供的字段 factory Highlight.create({ required String bookId, required String selectedText, HighlightColor color = HighlightColor.yellow, // 提供默认值 String? chapterId, }) { return Highlight( id: _generateId(), // 自动生成 createdTime: DateTime.now(), // 自动设置 // ... ); } ``` ## 四、性能优化技巧 ### 1. 字符串插值优于字符串拼接 ```dart // ❌ 性能较差 return 'Book(id: ' + id + ', title: ' + title + ')'; // ✅ 性能更好 return 'Book(id: $id, title: $title)'; ``` ### 2. 避免重复计算 ```dart class Highlight { // ❌ 每次调用都重新计算 int getTextLength() => endIndex - startIndex; // ✅ 使用getter缓存计算结果 int get textLength => endIndex - startIndex; } ``` ### 3. 合理使用const构造函数 ```dart // 对于常量值使用const static const List emptyList = []; // 构造函数标记为const const Book({required this.title}); ``` ## 五、调试与测试技巧 ### 1. 有意义的toString实现 ```dart @override String toString() { return 'Book(id: $id, title: $title, format: $format)'; } ``` ### 2. 完整的对象比较 ```dart @override bool operator ==(Object other) { if (identical(this, other)) return true; // 先检查引用相等 return other is Book && other.id == id; // 再检查关键字段 } @override int get hashCode => id.hashCode; // 基于相同字段生成 ``` ## 六、下一步计划 ### 待完成的模型: 1. **Annotation(批注模型)** - 处理用户笔记和感想 2. **Bookmark(书签模型)** - 管理阅读位置标记 3. **Bookshelf(书架模型)** - 书籍分类管理 4. **ReadingProgress(阅读进度模型)** - 跟踪阅读进度 ### 待学习的技术: 1. **本地存储方案** - Hive数据库集成 2. **Repository模式** - 数据访问层设计 3. **状态管理** - Provider/Riverpod集成 4. **文件处理** - EPUB/MOBI解析 ## 七、代码质量检查清单 ### ✅ 数据模型检查清单: - [ ] 所有字段使用final - [ ] 正确使用空值安全语法(?和required) - [ ] 实现copyWith方法并包含所有字段 - [ ] 正确的序列化(枚举转换) - [ ] 提供工厂方法简化创建 - [ ] 重写toString、equals、hashCode - [ ] 添加有意义的文档注释 - [ ] 提供默认值避免null - [ ] 使用计算属性优化性能