如何组织合理稳定的Flutter工程结构?
在软件开发中,我们不仅要在代码实现中遵守常见的设计模式,更需要在架构设计中遵从基本的设计原则。而在这其中,DRY(即 Don’t Repeat Yourself)原则可以算是最重要的一个。通俗来讲,DRY 原则就是“不要重复”。这是一个很朴素的概念,因为即使是最初级的开发者,在写了一段时间代码后,也会不自觉地把一些常用的重复代码抽取出来,放到公用的函数、类或是独立的组件库中,从而实现代码复用。在软件开发中,我们通常从架构设计中就要考虑如何去管理重复性(即代码复用),即如何将功能进行分治,将大问题分解为多个较为独立的小问题。而在这其中,组件化和平台化就是客户端开发中最流行的分治手段。
组件化
组件化又叫模块化,即基于可重用的目的,将一个大型软件系统(App)按照关注点分离的方式,拆分成多个独立的组件或模块。每个独立的组件都是一个单独的系统,可以单独维护、升级甚至直接替换,也可以依赖于别的独立组件,只要组件提供的功能不发生变化,就不会影响其他组件和软件系统的整体功能。
组件化的中心思想是将独立的功能进行拆分,而在拆分粒度上,组件化的约束则较为松散。一个独立的组件可以是一个软件包(Package)、页面、UI 控件,甚至可能是封装了一些函数的模块。
组件的粒度可大可小,那我们如何才能做好组件的封装重用呢?哪些代码应该被放到一个组件中?这里有一些基本原则,包括单一性原则、抽象化原则、稳定性原则和自完备性原则。
单一性原则指的是,每个组件仅提供一个功能。分而治之是组件化的中心思想,每个组件都有自己固定的职责和清晰的边界,专注地做一件事儿,这样这个组件才能良性发展。
一个反例是 Common 或 Util 组件,这类组件往往是因为在开发中出现了定义不明确、归属边界不清晰的代码:“哎呀,这段代码放哪儿好像都不合适,那就放 Common(Util)吧”。久而久之,这类组件就变成了无人问津的垃圾堆。所以,遇到不知道该放哪儿的代码时,就需要重新思考组件的设计和职责了。
抽象化原则指的是,组件提供的功能抽象应该尽量稳定,具有高复用度。而稳定的直观表现就是对外暴露的接口很少发生变化,要做到这一点,需要我们提升对功能的抽象总结能力,在组件封装时做好功能抽象和接口设计,将所有可能发生变化的因子都在组件内部做好适配,不要暴露给它的调用方。
稳定性原则指的是,不要让稳定的组件依赖不稳定的组件。比如组件 1 依赖了组件 5,如果组件 1 很稳定,但是组件 5 经常变化,那么组件 1 也就会变得不稳定了,需要经常适配。如果组件 5 里确实有组件 1 不可或缺的代码,我们可以考虑把这段代码拆出来单独做成一个新的组件 X,或是直接在组件 1 中拷贝一份依赖的代码。
自完备性,即组件需要尽可能地做到自给自足,尽量减少对其他底层组件的依赖,达到代码可复用的目的。比如,组件 1 只是依赖某个大组件 5 中的某个方法,这时更好的处理方法是,剥离掉组件 1 对组件 5 的依赖,直接把这个方法拷贝到组件 1 中。这样一来组件 1 就能够更好地应对后续的外部变更了。
我们再来看看组件化的具体实施步骤
首先,我们需要剥离应用中与业务无关的基础功能,比如网络请求、组件中间件、第三方库封装、UI 组件等,将它们封装为独立的基础库;然后,我们在项目里用 pub 进行管理。如果是第三方库,考虑到后续的维护适配成本,我们最好再封装一层,使项目不直接依赖外部代码,方便后续更新或替换。
基础功能已经封装成了定义更清晰的组件,接下来就可以按照业务维度,比如首页、详情页、搜索页等,去拆分独立的模块了。拆分的粒度可以先粗后细,只要能将大体划分清晰的业务组件进行拆分,后续就可以通过分布迭代、局部微调,最终实现整个业务项目的组件化。
在业务组件和基础组件都完成拆分封装后,应用的组件化架构就基本成型了,最后就可以按照刚才我们说的 4 个原则,去修正各个组件向下的依赖,以及最小化对外暴露的能力了。
Flutter 项目组件化(模块化)实现
下面以命令行(CLI)为主,演示如何在 Flutter 中快速搭建一个多模块工程。
一、目录规划
建议项目结构如下:
bash
代码解读
复制代码
/my_app # 主工程 ├── pubspec.yaml └── lib /modules # 存放各功能模块 ├── auth_module └── profile_module
二、创建主工程
bash
代码解读
复制代码
# 在任意工作目录下 flutter create my_app cd my_app
三、创建 Dart 功能包模块
我们使用 --template=package 来生成纯 Dart 包
ini
代码解读
复制代码
# 回到项目根目录 cd .. # 创建存放模块的文件夹 mkdir modules cd modules # 创建两个功能模块:auth_module、profile_module flutter create --template=package --org com.example auth_module flutter create --template=package --org com.example profile_module
此时,modules/auth_module/lib/ 下已有一个示例 Dart 文件。你可以在各模块内按需组织代码、资源、依赖。
四、在主工程中引入模块
编辑 my_app/pubspec.yaml,在 dependencies: 下加入本地路径依赖:
yaml
代码解读
复制代码
dependencies: flutter: sdk: flutter # 本地模块依赖 auth_module: path: ../modules/auth_module profile_module: path: ../modules/profile_module
执行:
arduino
代码解读
复制代码
cd my_app flutter pub get
这样,主工程就可以:
arduino
代码解读
复制代码
import 'package:auth_module/auth_module.dart'; import 'package:profile_module/profile_module.dart';
五、Android 平台配置
若模块中含有 Android 原生代码(如使用 --template=plugin 创建的模块),需在主工程的 android/settings.gradle 和 android/app/build.gradle 中注册子项目。
-
打开 my_app/android/settings.gradle,末尾添加:
php
代码解读
复制代码
include ':auth_module' project(':auth_module').projectDir = file('../modules/auth_module/android') include ':profile_module' project(':profile_module').projectDir = file('../modules/profile_module/android') -
打开 my_app/android/app/build.gradle,在 dependencies 中引入:
java
代码解读
复制代码
dependencies { implementation project(':auth_module') implementation project(':profile_module') // ... 其他依赖 } -
再次同步并运行:
arduino
代码解读
复制代码
cd my_app flutter clean flutter run
六、iOS 平台配置
同理,若模块内含 iOS 原生代码,需要在主工程的 iOS Podfile 中添加路径依赖:
-
打开 my_app/ios/Podfile,在 target 'Runner' do 内加入:
ini
代码解读
复制代码
pod 'auth_module', :path => '../modules/auth_module/ios' pod 'profile_module', :path => '../modules/profile_module/ios' -
安装 Pod:
bash
代码解读
复制代码
cd my_app/ios pod install -
回到根目录,运行:
arduino
代码解读
复制代码
cd .. flutter clean flutter run
七、模块化开发流程建议
-
代码隔离
- 每个模块内部只关心自身功能,公用工具抽到 common 或 core 模块。
-
版本管理
- 模块也可以发布到私有或公有 Dart 仓库,使用版本号管理依赖。
-
单元测试与 CI
- 各模块独立维护测试用例,CI Pipelines 可针对单模块或全量运行。
通过以上步骤,即可快速搭建一个基于 CLI 的 Flutter 多模块工程,实现关注点分离、可复用、易维护的组件化结构。
八、如何将一个 Flutter/Dart 模块发布到公有仓库(pub.dev),以及如何在主工程中使用语义化版本号管理依赖。
1. 在 pub.dev 上创建 API Token
- 打开 pub.dev ,登录你的 Google/GitHub 帐号。
- 点击右上角头像 → Account → API tokens → Create token。
- 复制生成的 token(形如 xxxxxxxxxxxxxxxxxxxxxxxx),后续在本地配置使用。
2. 在本地添加认证凭据
方法 A:使用 dart pub token(推荐)
macOS 终端执行:
csharp
代码解读
复制代码
# 将 替换为上一步获得的 token dart pub token add --server=https://pub.dev
- 该命令会把凭据写入 ~/.config/dart/pub-credentials.json,以后 dart pub publish 或 flutter pub publish 都能自动生效。
方法 B:环境变量(不太常用)
ini
代码解读
复制代码
export PUB_TOKEN= # 可选:把这一行加到 ~/.zshrc 或 ~/.bash_profile 以便每次登录都生效
3. 配置模块的 pubspec.yaml
在你的模块目录(如 modules/auth_module)下,确保 pubspec.yaml 包含:
yaml
代码解读
复制代码
name: auth_module description: A reusable authentication module for Flutter apps. version: 1.0.0 # 遵循 SemVer homepage: https://example.com/auth_module # 可选 author: Your Name # 可选 publish_to: https://pub.dev # 发布到公有仓库(也可删掉此行,默认为 pub.dev)
-
version:MAJOR.MINOR.PATCH
- MAJOR:不兼容的 API 变更
- MINOR:向下兼容的新功能
- PATCH:向下兼容的 Bug 修复
4. 预检发布(dry run)
在模块根目录运行:
bash
代码解读
复制代码
cd modules/auth_module dart pub publish --dry-run
- 修正 pubspec.yaml 警告(如缺少描述、缺少 LICENSE 等)。
- 确保所有示例代码、README、CHANGELOG 都准备齐全。
5. 正式发布
rust
代码解读
复制代码
dart pub publish
- 如果使用 Flutter 项目,也可运行 flutter pub publish,效果一样。
- 按提示确认,一旦发布成功,你的包就能在 pub.dev/ 上找到。
6. 在主工程中使用版本号管理依赖
编辑主工程 pubspec.yaml,在 dependencies: 下加入:
yaml
代码解读
复制代码
dependencies: flutter: sdk: flutter # 使用 caret 语法,接受 1.0.x 的所有版本,但 =1.0.0
-