在 SOLID 原则中,“D” 指的是 依赖倒置原则(Dependency Inversion Principle,DIP),它倡导:
高层模块不应依赖低层模块,二者都应该依赖抽象。抽象不应依赖细节,细节应依赖抽象。
换句话说,我们不应该在高层逻辑中直接依赖具体实现,而应依赖接口或抽象类。这可以大幅提升代码的可扩展性、可测试性和模块解耦能力。
🚫 常见反例:直接依赖具体实现
我们以一个“文章服务”为例,展示 Flutter 中常见的错误做法:
class ArticleService {
Future<List<String>> fetchArticles() async {
// 模拟从网络获取文章
await Future.delayed(Duration(seconds: 1));
return ['文章 A', '文章 B'];
}
}
class HomePage extends StatelessWidget {
final service = ArticleService(); // 👈 错误:UI 层直接依赖具体类
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: service.fetchArticles(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return ListView(
children: snapshot.data!.map((e) => Text(e)).toList(),
);
},
);
}
}
😵 问题点分析:
- UI 直接依赖具体类
ArticleService,导致 耦合严重,难以测试或替换。 - 如果将来需要改为从本地缓存读取文章或接入 GraphQL,需要改动 UI。
- 违背了依赖倒置原则,造成模块层级混乱。
✅ 正确做法:依赖抽象接口
1. 抽象定义(domain/article_repository.dart)
abstract class ArticleRepository {
Future<List<String>> fetchArticles();
}
2. 实现类(infrastructure/remote_article_repository.dart)
class RemoteArticleRepository implements ArticleRepository {
@override
Future<List<String>> fetchArticles() async {
await Future.delayed(Duration(seconds: 1));
return ['文章 A', '文章 B'];
}
}
3. UI 依赖抽象(application/home_controller.dart)
class HomeController {
final ArticleRepository repository;
HomeController(this.repository);
Future<List<String>> getArticles() => repository.fetchArticles();
}
🚀 结合 Riverpod 实现依赖倒置
我们通过 Riverpod 注入 抽象接口绑定的实现类,完成解耦与依赖管理:
1. Provider 注入(infrastructure/article_provider.dart)
final articleRepositoryProvider = Provider<ArticleRepository>((ref) {
return RemoteArticleRepository(); // 可轻松替换为 Mock 或其他实现
});
final homeControllerProvider = Provider<HomeController>((ref) {
final repository = ref.read(articleRepositoryProvider);
return HomeController(repository);
});
2. UI 使用(presentation/home_page.dart)
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.read(homeControllerProvider);
return FutureBuilder<List<String>>(
future: controller.getArticles(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView(
children: snapshot.data!.map((e) => Text(e)).toList(),
);
},
);
}
}
✅ 优势:
- UI 完全解耦,只关注
HomeController。 - 将来的实现可以轻松替换为缓存、本地数据库、Fake 模拟等。
- 可单元测试、集成测试,依赖由外部注入。
🧪 轻松测试你的业务逻辑
class FakeArticleRepository implements ArticleRepository {
@override
Future<List<String>> fetchArticles() async => ['测试文章 A', '测试文章 B'];
}
void main() {
test('HomeController fetches articles', () async {
final controller = HomeController(FakeArticleRepository());
final articles = await controller.getArticles();
expect(articles.length, 2);
});
}
📌 总结:依赖倒置的意义与实践
依赖倒置原则的目标是降低模块之间的耦合,让高层逻辑更稳定、底层实现更灵活。在实际开发中,我们应牢记以下几点:
🎯 实战重点
| 场景 | DIP 应用方式 |
|---|---|
| 需要替换网络服务实现 | 依赖抽象 ArticleRepository,替换不动上层逻辑 |
| 测试时避免请求真实网络 | 使用 FakeRepository 注入控制结果 |
| 组件间解耦 | 通过 Provider 注入抽象而非直接创建类实例 |
| 多平台(Web / App)支持差异逻辑 | 抽象接口 + 不同平台实现类,运行时自由切换 |
💬 一句话总结:
DIP 不是“强行抽象”,而是“依赖更稳定的抽象,释放更灵活的实现”。
用抽象隔离变动,用 Provider 管理依赖。写出更优雅、测试友好、可扩展的 Flutter 代码,从依赖倒置原则开始。
如果你觉得这篇文章对你有帮助,欢迎点个 “在看”,或转发给身边的 Flutter 同学,一起写出更优雅、可维护的代码!