2. 共享业务逻辑层与平台适配层拆分
为什么需要拆分共享逻辑与平台适配
跨平台项目的核心矛盾是:业务规则(登录校验、数据格式、错误码)在所有平台完全一致,而平台能力(安全存储、网络栈、文件路径、弹窗)各不相同。如果把这些混在一个文件里,Android 的改动会污染 iOS 与 Web。正确做法是依赖倒置:共享层只定义"我需要什么能力"的抽象接口,各平台提供最小实现。
共享层:定义契约与纯业务逻辑
共享层不引用任何平台 API。它包含两类内容:纯业务逻辑(可直接运行、可单元测试)和抽象接口(声明平台必须提供的能力)。以登录为例,共享层定义如下契约:
// shared/auth/token_storage.dart —— 抽象接口,无平台依赖
abstract class TokenStorage {
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
}
// shared/auth/auth_service.dart —— 纯业务逻辑
class AuthService {
final TokenStorage _storage;
final HttpClient _http;
AuthService(this._storage, this._http);
Future<LoginResult> login(String account, String password) async {
// 1. 本地校验(纯逻辑,不依赖平台)
if (!Validator.isEmail(account) && !Validator.isPhone(account)) {
return LoginResult.fail('账号格式不正确');
}
if (password.length < 8) {
return LoginResult.fail('密码至少 8 位');
}
// 2. 调用网络(通过抽象接口)
final resp = await _http.post('/api/login', {
'account': account,
'password': password,
});
if (resp.statusCode != 200) {
return LoginResult.fail('登录失败:${resp.statusCode}');
}
// 3. 持久化 token(通过抽象接口)
await _storage.write('token', resp.body['token']);
return LoginResult.ok(resp.body['user']);
}
}
// shared/validator.dart —— 纯函数,零依赖
class Validator {
static final _emailRe = RegExp(r'^[\w.+-]+@[\w-]+\.[\w.]+$');
static final _phoneRe = RegExp(r'^1[3-9]\d{9}$');
static bool isEmail(String s) => _emailRe.hasMatch(s);
static bool isPhone(String s) => _phoneRe.hasMatch(s);
}
共享层的关键约束:不 import 任何平台包(如 dart:io、dart:html、package:flutter)。这样它才能同时被移动端、Web 和桌面端复用,也才能在纯 Dart VM 中直接跑单元测试。
平台适配层:每个平台只写最小实现
适配层只做一件事:把抽象接口映射到平台原生能力。每个平台通常只需 20~50 行代码。以下展示 Android 与 Web 两个典型实现:
// adapters/android/token_storage_android.dart
import 'package:shared_preferences/shared_preferences.dart';
import '../../shared/auth/token_storage.dart';
class AndroidTokenStorage implements TokenStorage {
@override
Future<String?> read(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
@override
Future<void> write(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
}
@override
Future<void> delete(String key) async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(key);
}
}
// adapters/web/token_storage_web.dart
import 'dart:html' as html;
import '../../shared/auth/token_storage.dart';
class WebTokenStorage implements TokenStorage {
@override
Future<String?> read(String key) async => html.window.localStorage[key];
@override
Future<void> write(String key, String value) async {
html.window.localStorage[key] = value;
}
@override
Future<void> delete(String key) async {
html.window.localStorage.remove(key);
}
}
组装:在入口处注入平台实现
共享层不关心谁实现了接口,由各平台入口负责"接线"。这是依赖注入最简单的形式——构造函数注入:
// main_android.dart
void main() {
final auth = AuthService(AndroidTokenStorage(), AndroidHttpClient());
runApp(App(auth: auth));
}
// main_web.dart
void main() {
final auth = AuthService(WebTokenStorage(), WebHttpClient());
runApp(App(auth: auth));
}
验证拆分是否成功
| 检查项 | 通过标准 | 验证方式 |
|---|---|---|
| 共享层零平台依赖 | shared/ 下无 dart:io、dart:html、flutter 导入 | grep 搜索 import 语句 |
| 业务逻辑可独立测试 | AuthService 用 Mock 实现即可跑通 | 纯 Dart VM 运行单元测试 |
| 适配层足够薄 | 每个平台实现类不超过 60 行 | 代码行数统计 |
| 新增平台成本低 | 只需新增一个 adapters/xxx/ 目录 | 不改动 shared/ 任何文件 |
拆分后的收益:登录逻辑只写一次,Android 换用 EncryptedSharedPreferences 时只改 AndroidTokenStorage,iOS 和 Web 完全不受影响;新增桌面端时只需补一个 DesktopTokenStorage,共享层零改动。