如何在 Flutter 中实现乐高架构 [完整手册]

如何在 Flutter 中实现乐高架构 [完整手册]

💡 原文英文,约7500词,阅读约需28分钟。
📝

内容提要

本文用乐高积木比喻 Flutter 代码架构:组件通过抽象契约连接,不依赖内部实现。从 Widget 组合、抽象类依赖注入,到按功能分文件夹、模块化 Monorepo,逐步提升可替换性与可测试性。Clean Architecture 是其中一种固定分层实现,二者本质相同,可按项目复杂度灵活选用。

🔎

延伸解读

从 Widget 到模块:乐高架构的渐进式落地

文章强调乐高架构并非一次性重构,而是从 Widget 组合开始,逐步扩展到抽象契约、按功能分文件夹、模块化 Monorepo。每个阶段都只增加少量新概念,开发者可按项目复杂度选择停在某一层。这种渐进性降低了学习成本,也避免了过度设计,尤其适合团队逐步采纳。

契约与依赖注入:可替换性的关键机制

文章通过 CartWriter、ProductLookup 等抽象类展示契约如何隔离实现细节,并借助 get_it 服务定位器在单一装配点注入具体实现。这样,测试时可传入内存实现,生产环境切换为 API 实现,而调用方无需改动。这种模式是乐高架构实现可替换性与可测试性的核心。

乐高架构与 Clean Architecture 的取舍

文章指出两者都基于依赖倒置,但乐高架构是灵活的心智模型,边界和粒度由开发者决定;Clean Architecture 则是固定分层(表现、领域、数据),强制依赖规则向内。乐高架构起步简单、样板少,适合需要灵活边界的项目;Clean Architecture 更适合业务规则复杂、需严格独立于框架的场景。选择在于应用多少固定形状,而非二选一。

模块化 Monorepo:用包系统强制边界

文章演示了如何将每个功能拆为独立 Dart 包,通过 path 依赖和 Melos 管理。包边界由 pubspec.yaml 强制,编译器阻止跨包内部导入,比文件夹约定更可靠。Melos 仅简化多包命令执行,不创建边界。app_main 仅保留启动、依赖注入和路由组装,成为唯一知晓所有功能的组合根。

Q&A

什么是 Flutter 中的乐高架构?

乐高架构是一种将代码像乐高积木一样构建的思维方式:每个组件(Widget、类、服务或整个功能)只通过抽象契约(如抽象类或接口)与外界连接,不依赖内部实现。这样组件可以独立替换、测试和复用,就像乐高积木通过凸点连接,不需要知道彼此内部构造。

如何在 Widget 层面应用乐高架构?

在 Widget 层面,乐高架构意味着将 UI 拆分成职责单一的小组件,并通过 child 参数等标准接口组合。例如,将绘制圆角阴影盒子的逻辑抽成 SurfaceCard,它只负责外观,接受任意 child;PriceTag 则只负责显示价格,使用 SurfaceCard 包裹文本。这样 SurfaceCard 可复用于任何内容,无需修改。

乐高架构中如何实现依赖注入和契约?

通过抽象类定义契约(如 CartWriter),具体实现(如 ApiCartWriter 或 InMemoryCartWriter)实现该契约。组件通过构造函数接收契约实例(依赖注入),而不是自己实例化具体类。这样可以在测试中传入假实现,生产中使用真实实现,实现可替换性和可测试性。

乐高架构和 Clean Architecture 有什么区别?

乐高架构是一种关于可组合性和边界的思维方式,单元可自由选择(Widget、服务或功能模块),边界灵活,学习曲线低,按需增加样板代码。Clean Architecture 是一种固定的分层方案(表现层、领域层、数据层),依赖规则始终向内,单元固定为实体、用例和仓库,学习曲线陡峭,样板代码多,适合复杂业务逻辑。两者本质都基于依赖倒置,Clean Architecture 是乐高架构在单个功能内的一种具体实现。

如何在 Flutter 中搭建模块化 Monorepo?

首先创建项目根目录,在 apps 文件夹下用 flutter create 创建主应用(包含原生文件夹),在 packages 文件夹下用 flutter create --template=package 创建功能包(无原生文件夹)。然后在主应用的 pubspec.yaml 中添加对功能包的路径依赖(如 path: ../../packages/feature_login)。最后在根目录创建 melos.yaml,定义 packages 为 apps/** 和 packages/**,运行 melos bootstrap 即可统一管理所有包的依赖。

在乐高架构的 Monorepo 中,app_main 的 lib 文件夹应该放什么?

app_main/lib 只保留三个职责:1) main.dart,启动应用并调用 runApp();2) 依赖注入设置,即组合根,创建具体实现并注册到服务定位器;3) 组装所有功能模块的路由和依赖。功能的具体实现(屏幕、状态管理、业务逻辑)都放在各自的 feature 包中,app_main 只负责组装。

🏷️

标签

➡️

继续阅读