← 返回首页

跨平台开发最佳实践:架构设计与代码组织

总结多个项目后的架构经验:目录结构、分层设计、共享逻辑与平台差异隔离,打造可维护的大型跨平台项目。

目录结构

好的目录结构是可维护性的基础。我在多个项目中验证过的结构(以 Flutter 为例):

lib/
├── core/                    # 核心基础设施
│   ├── api/                 # HTTP 客户端、拦截器
│   ├── storage/             # 本地存储抽象
│   ├── router/              # 路由配置
│   └── di/                  # 依赖注入
│
├── features/                # 按功能划分(Feature-First)
│   ├── auth/
│   │   ├── data/            # 数据层:API、本地DB
│   │   ├── domain/          # 领域层:实体、用例
│   │   └── presentation/    # 表现层:页面、Widget、Provider
│   ├── home/
│   └── profile/
│
├── shared/                  # 跨功能共用
│   ├── widgets/             # 通用 UI 组件
│   ├── utils/               # 工具函数
│   └── constants/           # 常量定义
│
└── main.dart

关键原则:按功能(Feature)划分,而非按类型(Type)划分。按类型划分(pages/、models/、services/)在项目变大后会造成大量跨目录的依赖,修改一个功能要改四五个文件夹。

分层架构(Clean Architecture 简化版)

对于中大型项目,我推荐三层架构:

┌─────────────────────────────┐
│   Presentation Layer         │  UI、ViewModel/Provider/BLoC
├─────────────────────────────┤
│   Domain Layer               │  业务逻辑、实体、Repository 接口
├─────────────────────────────┤
│   Data Layer                 │  API、数据库、Repository 实现
└─────────────────────────────┘
  • Domain 层不依赖任何框架,只有纯 Dart/TypeScript 代码。这让它可以被独立测试,也方便未来迁移框架。
  • Repository 模式:Domain 层定义接口,Data 层实现。这样切换 API 地址、更换数据库引擎不会影响业务逻辑。
// domain/repositories/user_repository.dart(接口)
abstract class UserRepository {
  Future<User> getUserById(String id);
  Future<void> updateProfile(User user);
}

// data/repositories/user_repository_impl.dart(实现)
class UserRepositoryImpl implements UserRepository {
  final ApiClient _api;
  final LocalDatabase _db;

  @override
  Future<User> getUserById(String id) async {
    // 先查缓存,没有再请求 API
    final cached = await _db.getUser(id);
    if (cached != null) return cached;
    final user = await _api.fetchUser(id);
    await _db.saveUser(user);
    return user;
  }
}

平台差异隔离

有些 UI 或行为在 Android 和 iOS 上应该不同(如 iOS 的 Cupertino 风格组件)。隔离平台差异而非到处写 if Platform.isIOS

// shared/widgets/platform_dialog.dart
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

Future<bool?> showPlatformDialog({
  required BuildContext context,
  required String title,
  required String message,
}) {
  if (Platform.isIOS) {
    return showCupertinoDialog<bool>(
      context: context,
      builder: (_) => CupertinoAlertDialog(
        title: Text(title),
        content: Text(message),
        actions: [
          CupertinoDialogAction(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)),
          CupertinoDialogAction(isDefaultAction: true, child: const Text('确定'), onPressed: () => Navigator.pop(context, true)),
        ],
      ),
    );
  }
  return showDialog<bool>(
    context: context,
    builder: (_) => AlertDialog(
      title: Text(title),
      content: Text(message),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
        TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('确定')),
      ],
    ),
  );
}

共享业务逻辑

跨平台框架最大的价值就是共享逻辑。以下这些内容应该尽量平台无关:

  • 数据模型(User、Product、Order)
  • API 请求与响应处理
  • 本地存储的 key 定义
  • 日期格式化、货币转换等工具函数
  • 表单验证规则
  • 业务状态机(如订单状态流转)

而以下内容应该允许平台差异:

  • 导航手势(Android 返回键 vs iOS 滑动返回)
  • 分享功能(平台原生分享面板)
  • 通知权限申请时机和文案
  • 支付(Google Pay vs Apple Pay)

测试策略

跨平台项目的测试金字塔:

          /\
         /  \
        / E2E\      集成测试:patrol / detox
       /______\
      / Widget  \   Widget 测试:flutter_test
     /____________\
    /   Unit Tests  \ 单元测试:Domain 层逻辑,100% 覆盖率
   /________________\

Domain 层的用例(Use Case)是最值得写测试的地方,它们是纯函数,没有 Flutter/React Native 依赖,测试简单快速:

// test/domain/get_user_test.dart
void main() {
  late GetUserUseCase useCase;
  late MockUserRepository mockRepo;

  setUp(() {
    mockRepo = MockUserRepository();
    useCase = GetUserUseCase(mockRepo);
  });

  test('应该返回用户数据', () async {
    when(mockRepo.getUserById('123'))
        .thenAnswer((_) async => User(id: '123', name: 'Alice'));
    final result = await useCase.execute('123');
    expect(result.name, 'Alice');
  });
}

CI/CD 自动化

GitHub Actions 是独立开发者的最佳选择,免费额度足够用。一个基础的 Flutter CI 工作流:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test --coverage

  build-android:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
      - run: flutter build appbundle --release

核心原则

「不要为了跨平台而跨平台,为了用户体验而正确处理平台差异。」

  • 功能优先于完美架构:先让 App 能用,再重构
  • 延迟抽象:当你第三次写重复代码时再抽象,不要过早
  • 平台一致 ≠ 平台相同:用户期望 iOS App 有 iOS 的感觉,Android App 有 Android 的感觉
  • 真机测试不可省:模拟器无法完全模拟性能、传感器和系统交互
  • 持续集成是底线:每次提交都跑测试,能省下大量调试时间