柳州市网站建设_网站建设公司_前端工程师_seo优化
2025/12/23 23:27:18 网站建设 项目流程

Harmony之路:实战起航(二)——数据模型与业务逻辑封装

引入

在上一篇中,我们搭建了清晰的项目结构,为应用奠定了坚实的架构基础。今天,我们将深入实战项目的核心——数据模型与业务逻辑封装。如果说项目结构是应用的骨架,那么数据模型就是血肉,业务逻辑则是神经中枢。合理的分层设计能让代码更易维护、测试和扩展,避免"面条式代码"的噩梦。

想象一下,当你的应用需要从本地存储切换到云端存储,或者需要支持多设备数据同步时,如果业务逻辑与数据源紧密耦合,修改成本将呈指数级增长。而通过今天学习的数据层抽象业务逻辑封装,这些问题都能迎刃而解。

讲解

一、数据模型设计原则

在HarmonyOS应用中,数据模型设计遵循单一数据源(SSOT)原则,即每种数据类型都应该有且只有一个权威的数据源。这能确保数据一致性,避免数据冲突。

数据模型分层架构:

应用层
├── UI组件(页面、组件)
├── 业务逻辑层(ViewModel、Service)
└── 数据层├── 数据源抽象(Repository)├── 本地数据源(Preferences、数据库)└── 远程数据源(网络API)

二、实体类设计

首先定义应用的核心数据模型。以待办事项应用为例:

// models/Todo.ts
export interface Todo {id: string;title: string;description?: string;completed: boolean;createdAt: Date;updatedAt: Date;
}export interface TodoCreateParams {title: string;description?: string;
}

设计要点:

  • 使用接口而非类,保持数据不可变性
  • 区分实体类和创建参数类,避免污染实体属性
  • 使用可选属性(?)标记非必填字段

三、数据源抽象层(Repository模式)

Repository模式是数据层设计的核心,它对外提供统一的数据访问接口,隐藏底层数据源的实现细节。

// data/repositories/TodoRepository.ts
export interface TodoRepository {getAll(): Promise<Todo[]>;getById(id: string): Promise<Todo | null>;create(todo: TodoCreateParams): Promise<Todo>;update(id: string, updates: Partial<Todo>): Promise<Todo>;delete(id: string): Promise<void>;toggleComplete(id: string): Promise<Todo>;
}

接口设计的优势:

  • 支持多数据源(本地、网络、内存缓存)
  • 便于单元测试(Mock实现)
  • 支持热切换数据源(如离线优先策略)

四、本地数据源实现

4.1 使用Preferences存储

对于轻量级配置数据,使用Preferences是最佳选择:

// data/sources/LocalTodoSource.ts
import preferences from '@ohos.data.preferences';
import { Todo, TodoCreateParams } from '../models/Todo';export class LocalTodoSource {private static readonly STORE_NAME = 'todos';private static readonly KEY_TODOS = 'todos_list';private prefs: preferences.Preferences | null = null;async initialize(context: common.Context): Promise<void> {this.prefs = await preferences.getPreferences(context, LocalTodoSource.STORE_NAME);}async getAll(): Promise<Todo[]> {if (!this.prefs) throw new Error('Preferences not initialized');const json = await this.prefs.get(LocalTodoSource.KEY_TODOS, '[]');return JSON.parse(json.toString());}async saveAll(todos: Todo[]): Promise<void> {if (!this.prefs) throw new Error('Preferences not initialized');await this.prefs.put(LocalTodoSource.KEY_TODOS, JSON.stringify(todos));await this.prefs.flush();}
}

4.2 使用关系型数据库

对于结构化数据,关系型数据库更合适:

// data/sources/DatabaseTodoSource.ts
import relationalStore from '@ohos.data.relationalStore';
import { Todo, TodoCreateParams } from '../models/Todo';export class DatabaseTodoSource {private static readonly DB_NAME = 'todo.db';private static readonly TABLE_NAME = 'todos';private rdbStore: relationalStore.RdbStore | null = null;async initialize(context: common.Context): Promise<void> {const config: relationalStore.StoreConfig = {name: DatabaseTodoSource.DB_NAME,securityLevel: relationalStore.SecurityLevel.S1};this.rdbStore = await relationalStore.getRdbStore(context, config);// 创建表const sql = `CREATE TABLE IF NOT EXISTS ${DatabaseTodoSource.TABLE_NAME} (id TEXT PRIMARY KEY,title TEXT NOT NULL,description TEXT,completed INTEGER NOT NULL DEFAULT 0,createdAt TEXT NOT NULL,updatedAt TEXT NOT NULL)`;await this.rdbStore.executeSql(sql);}async getAll(): Promise<Todo[]> {if (!this.rdbStore) throw new Error('Database not initialized');const predicates = new relationalStore.RdbPredicates(DatabaseTodoSource.TABLE_NAME);const result = await this.rdbStore.query(predicates);const todos: Todo[] = [];while (result.goToNextRow()) {todos.push({id: result.getString(result.getColumnIndex('id')),title: result.getString(result.getColumnIndex('title')),description: result.getString(result.getColumnIndex('description')),completed: result.getLong(result.getColumnIndex('completed')) === 1,createdAt: new Date(result.getString(result.getColumnIndex('createdAt'))),updatedAt: new Date(result.getString(result.getColumnIndex('updatedAt')))});}return todos;}
}

五、网络数据源实现

// data/sources/RemoteTodoSource.ts
import http from '@ohos.net.http';
import { Todo, TodoCreateParams } from '../models/Todo';export class RemoteTodoSource {private static readonly BASE_URL = 'https://api.example.com/todos';private httpClient: http.HttpClient;constructor() {this.httpClient = http.createHttp();}async getAll(): Promise<Todo[]> {const response = await this.httpClient.request(`${RemoteTodoSource.BASE_URL}`,{ method: http.RequestMethod.GET });if (response.responseCode !== 200) {throw new Error(`HTTP ${response.responseCode}: ${response.result}`);}return JSON.parse(response.result.toString());}async create(todo: TodoCreateParams): Promise<Todo> {const response = await this.httpClient.request(`${RemoteTodoSource.BASE_URL}`,{method: http.RequestMethod.POST,header: { 'Content-Type': 'application/json' },extraData: JSON.stringify(todo)});if (response.responseCode !== 201) {throw new Error(`HTTP ${response.responseCode}: ${response.result}`);}return JSON.parse(response.result.toString());}
}

六、Repository实现

将多个数据源组合成统一的Repository:

// data/repositories/TodoRepositoryImpl.ts
import { Todo, TodoCreateParams } from '../models/Todo';
import { TodoRepository } from './TodoRepository';
import { LocalTodoSource } from '../sources/LocalTodoSource';
import { RemoteTodoSource } from '../sources/RemoteTodoSource';export class TodoRepositoryImpl implements TodoRepository {private localSource: LocalTodoSource;private remoteSource: RemoteTodoSource;private isOnline: boolean = false;constructor(context: common.Context) {this.localSource = new LocalTodoSource();this.remoteSource = new RemoteTodoSource();// 初始化本地数据源this.localSource.initialize(context);// 监听网络状态this.checkNetworkStatus();}async getAll(): Promise<Todo[]> {if (this.isOnline) {try {const todos = await this.remoteSource.getAll();await this.localSource.saveAll(todos);return todos;} catch (error) {console.warn('Failed to fetch from remote, falling back to local', error);}}return this.localSource.getAll();}async create(todo: TodoCreateParams): Promise<Todo> {const newTodo: Todo = {id: this.generateId(),title: todo.title,description: todo.description,completed: false,createdAt: new Date(),updatedAt: new Date()};if (this.isOnline) {try {const created = await this.remoteSource.create(todo);await this.localSource.saveTodo(created);return created;} catch (error) {console.warn('Failed to create on remote, saving locally', error);}}await this.localSource.saveTodo(newTodo);return newTodo;}private generateId(): string {return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;}private async checkNetworkStatus(): Promise<void> {// 实际项目中应使用@ohos.net.connection监听网络状态this.isOnline = true; // 简化实现}
}

七、业务逻辑层封装

业务逻辑层负责处理复杂的业务规则,将数据层与UI层解耦:

// business/TodoService.ts
import { Todo, TodoCreateParams } from '../models/Todo';
import { TodoRepository } from '../data/repositories/TodoRepository';export class TodoService {constructor(private repository: TodoRepository) {}async getTodos(): Promise<Todo[]> {return this.repository.getAll();}async addTodo(params: TodoCreateParams): Promise<Todo> {if (!params.title.trim()) {throw new Error('Title cannot be empty');}if (params.title.length > 100) {throw new Error('Title cannot exceed 100 characters');}return this.repository.create(params);}async toggleTodo(id: string): Promise<Todo> {const todo = await this.repository.getById(id);if (!todo) {throw new Error('Todo not found');}return this.repository.update(id, {completed: !todo.completed,updatedAt: new Date()});}async deleteTodo(id: string): Promise<void> {const todo = await this.repository.getById(id);if (!todo) {throw new Error('Todo not found');}return this.repository.delete(id);}
}

八、ViewModel层实现

ViewModel负责管理UI状态,响应式更新UI:

// viewmodels/TodoViewModel.ts
import { Todo, TodoCreateParams } from '../models/Todo';
import { TodoService } from '../business/TodoService';export class TodoViewModel {@State todos: Todo[] = [];@State loading: boolean = false;@State error: string | null = null;constructor(private todoService: TodoService) {}async loadTodos(): Promise<void> {this.loading = true;this.error = null;try {this.todos = await this.todoService.getTodos();} catch (err) {this.error = err instanceof Error ? err.message : 'Failed to load todos';} finally {this.loading = false;}}async addTodo(title: string): Promise<void> {this.error = null;try {await this.todoService.addTodo({ title });await this.loadTodos(); // 重新加载列表} catch (err) {this.error = err instanceof Error ? err.message : 'Failed to add todo';}}async toggleTodo(id: string): Promise<void> {try {await this.todoService.toggleTodo(id);// 本地更新,避免重新加载const index = this.todos.findIndex(todo => todo.id === id);if (index !== -1) {this.todos[index] = {...this.todos[index],completed: !this.todos[index].completed,updatedAt: new Date()};}} catch (err) {this.error = err instanceof Error ? err.message : 'Failed to toggle todo';}}
}

九、依赖注入与单例管理

为了便于管理和测试,使用单例模式管理服务实例:

// managers/ServiceManager.ts
import { TodoRepositoryImpl } from '../data/repositories/TodoRepositoryImpl';
import { TodoService } from '../business/TodoService';export class ServiceManager {private static instance: ServiceManager;private todoService: TodoService | null = null;private constructor() {}static getInstance(): ServiceManager {if (!ServiceManager.instance) {ServiceManager.instance = new ServiceManager();}return ServiceManager.instance;}getTodoService(context: common.Context): TodoService {if (!this.todoService) {const repository = new TodoRepositoryImpl(context);this.todoService = new TodoService(repository);}return this.todoService;}
}

十、UI层使用示例

// pages/TodoPage.ets
import { TodoViewModel } from '../viewmodels/TodoViewModel';
import { ServiceManager } from '../managers/ServiceManager';@Entry
@Component
struct TodoPage {private viewModel: TodoViewModel;aboutToAppear() {const todoService = ServiceManager.getInstance().getTodoService(getContext(this));this.viewModel = new TodoViewModel(todoService);this.viewModel.loadTodos();}build() {Column() {if (this.viewModel.loading) {LoadingComponent()} else if (this.viewModel.error) {ErrorComponent(this.viewModel.error)} else {TodoListComponent({todos: this.viewModel.todos,onToggle: (id: string) => this.viewModel.toggleTodo(id)})}}}
}

小结

通过本文的学习,我们掌握了HarmonyOS应用数据模型与业务逻辑封装的核心方法。分层架构让我们将数据存储、业务规则和UI展示彻底解耦,每个层各司其职:

数据层:负责数据持久化和网络通信,通过Repository模式提供统一接口

业务层:封装复杂的业务逻辑和验证规则,确保数据一致性

ViewModel层:管理UI状态,响应式更新界面

UI层:专注于界面展示和用户交互

这种架构设计带来了三大优势:

  1. 可测试性:每层都可以独立测试,Mock依赖项
  2. 可维护性:修改数据源或业务逻辑不影响其他层
  3. 可扩展性:轻松添加新功能或切换数据源

在下一篇文章中,我们将进入实战项目的第三环节——UI组件化与重构,教你如何将复杂的UI拆分为可复用的自定义组件,进一步提升代码质量和开发效率。敬请期待!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询