Dart中Clean架构下用单方法接口实现依赖倒置的最优方式
我正在实现Clean Architecture,因此需要使用用例(use cases)。最初定义了如下抽象接口类:
abstract interface class UploadAssetUseCase{ Future<(AssetUploadFailure?, Url?)> execute(Uint8List data); }
Dart分析器抛出警告:
不必要地使用抽象类。尝试将'execute'设为顶层函数并移除该类。
于是我改用typedef方式:
typedef UploadAssetUseCase= Future<(AssetUploadFailure?, Url?)> Function(Uint8List data);
这种方式具备可测试性,但需要额外创建抽象类用于Mock。不过当尝试实现它时:
class FirebaseStorageUploadAssetUseCase implements UploadAssetUseCase{ }
会抛出异常,因为UploadAssetUseCase是函数类型无法被实现。如果回到可实现的抽象类,又会触发之前的警告:
abstract interface class _IUploadAssedUseCase {//出现之前试图避免的警告 Future<(AssetUploadFailure?, Url?)> call( Uint8List document, ); } class FirebaseStorageUploadAssetUseCase implements _IUploadAssedUseCase{ const FirebaseStorageUploadAssetUseCase(this.storage); final FirebaseStorage storage; @override Future<(AssetUploadFailure?, Url?)> call( Uint8List document, ){ } }
请问在此场景下最优方案是什么?是否应忽略警告继续使用类而非顶层函数?还有其他可行方法吗?
我的analysis options 3.1.0配置如下:
analyzer: errors: invalid_use_of_visible_for_testing_member: error const_eval_throws_exception: ignore linter: rules: public_member_api_docs: false library_private_types_in_public_api: false lines_longer_than_80_chars: false
针对Clean Architecture中用例的场景,这里有几种实用方案:
1. 忽略警告或禁用对应lint规则
Clean Architecture的核心是依赖抽象,用抽象类定义用例接口完全合理。Dart分析器的警告只是针对单一方法抽象类的通用建议,并不适配架构设计场景。
- 临时忽略单类警告:在抽象类上添加注释
// ignore: unnecessary_abstract_class abstract interface class UploadAssetUseCase{ Future<(AssetUploadFailure?, Url?)> execute(Uint8List data); } - 全局禁用规则:在
analysis_options.yaml中添加配置,避免每个类都加注释linter: rules: # 保留原有规则... unnecessary_abstract_class: false
2. 使用typedef结合类的call方法实现
typedef无法被implements,但可以让类实现call方法匹配函数签名,既符合函数类型定义,又能通过类封装依赖(比如FirebaseStorage):
typedef UploadAssetUseCase = Future<(AssetUploadFailure?, Url?)> Function(Uint8List data); class FirebaseStorageUploadAssetUseCase { const FirebaseStorageUploadAssetUseCase(this.storage); final FirebaseStorage storage; // 实现与typedef匹配的call方法 Future<(AssetUploadFailure?, Url?)> call(Uint8List data) async { // 具体上传逻辑 } }
使用时直接把类实例当作函数调用:
final uploadUseCase = FirebaseStorageUploadAssetUseCase(FirebaseStorage.instance); final result = await uploadUseCase(data);
测试时可以直接Mock该函数类型,或创建测试实现类并实现call方法。
3. 保留抽象类并改用call方法
如果更倾向于用接口约束,保留抽象类并使用call方法,既能让类实例像函数一样调用,又符合Clean Architecture的抽象依赖原则:
// ignore: unnecessary_abstract_class abstract interface class UploadAssetUseCase { Future<(AssetUploadFailure?, Url?)> call(Uint8List data); } class FirebaseStorageUploadAssetUseCase implements UploadAssetUseCase { const FirebaseStorageUploadAssetUseCase(this.storage); final FirebaseStorage storage; @override Future<(AssetUploadFailure?, Url?)> call(Uint8List data) async { // 实现逻辑 } }
最优方案建议
对于Clean Architecture场景,方案1或方案3更贴合架构设计思想:抽象类能明确定义用例契约,后续扩展(比如新增方法、修改签名)时,所有实现类都会收到编译提示。若团队统一遵循Clean Architecture规范,全局禁用unnecessary_abstract_class规则是最省心的选择。
内容的提问来源于stack exchange,提问作者Luis Utrera

