Harmony之路:实战起航(二)——数据模型与业务逻辑封装
在上一篇中,我们搭建了清晰的项目结构,为应用奠定了坚实的架构基础。今天,我们将深入实战项目的核心——数据模型与业务逻辑封装。如果说项目结构是应用的骨架,那么数据模型就是血肉,业务逻辑则是神经中枢。合理的分层设计能让代码更易维护、测试和扩展,避免"面条式代码"的噩梦。想象一下,当你的应用需要从本地存储切换到云端存储,或者需要支持多设备数据同步时,如果业务逻辑与数据源紧密耦合,修改成本将呈指数级增长。
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层:专注于界面展示和用户交互
这种架构设计带来了三大优势:
- 可测试性:每层都可以独立测试,Mock依赖项
- 可维护性:修改数据源或业务逻辑不影响其他层
- 可扩展性:轻松添加新功能或切换数据源
在下一篇文章中,我们将进入实战项目的第三环节——UI组件化与重构,教你如何将复杂的UI拆分为可复用的自定义组件,进一步提升代码质量和开发效率。敬请期待!
更多推荐

所有评论(0)