为什么整洁架构通常要分离Repository与DataSource的实现?
在整洁架构的演示示例中,我们经常看到这样的设计:明明Retrofit的MyApi、Room的MyDao本身已是可通过依赖注入替换实例的接口,却仍要额外封装一层RemoteDataSource/LocalDataSource,实现仅做简单转发,而非让Repository直接依赖这些框架接口。这种设计的核心原因如下:
示例代码对比
// Retrofit框架接口 interface MyApi { @GET("somePath") fun getSome() : SomeResponse } // Room框架接口 @Dao interface MyDao { @Query("select * from mytable") fun getSome() : List<SomeEntity> } // 方式1:大多数示例的实现 interface RemoteDataSource { fun getSome() : SomeResponse } interface LocalDataSource { fun getSome() : List<SomeEntity> } class RemoteDataSourceImpl @Inject constructor(private val api : MyApi) : RemoteDataSource { override fun getSome() = api.getSome() } class LocalDataSourceImpl @Inject constructor(private val dao : MyDao) : LocalDataSource { override fun getSome() = dao.getSome() } class MyRepositoryImpl @Inject constructor(private val remote : RemoteDataSource, private val local : LocalDataSource) : MyRepository { override fun mySomeGetFunction() { return MySomeEntity(entity1 = remote.getSome().data, entity2 = local.getSome().firstOrNull()) } } // 方式2:直接依赖框架接口的实现 class MyRepositoryImpl @Inject constructor(private val api : MyApi, private val dao : MyDao) : MyRepository { override fun mySomeGetFunction() { return MySomeEntity(entity1 = api.getSome().data, entity2 = dao.getSome().firstOrNull()) } }
核心设计原因
隔离框架细节,坚守依赖倒置原则
整洁架构的核心要求是领域层(包括Repository定义)不依赖任何外部框架。MyApi和MyDao本质是绑定了Retrofit/Room的框架特定接口,带有框架专属注解和返回类型约束。如果直接让Repository依赖它们,一旦后续更换框架(比如把Retrofit换成OkHttp手写实现,或Room换成其他ORM),就必须修改Repository的依赖和逻辑,违反了「依赖抽象而非具体实现」的原则。而RemoteDataSource是我们自主定义的、完全贴合业务需求的抽象,和框架无关,底层更换实现时,Repository代码无需改动。拆分职责,避免Repository臃肿
实际业务中,数据操作不会只有单一接口调用:比如远程请求需要统一处理异常、添加重试逻辑、参数转换;本地数据可能需要做实体映射、缓存有效期校验。这些框架层面或通用数据逻辑放在RemoteDataSourceImpl/LocalDataSourceImpl中,能让Repository只专注于业务逻辑的组合(比如何时读本地、何时请求远程),符合单一职责原则。后续修改数据层的通用逻辑时,无需侵入Repository代码。简化测试,降低测试成本
虽然MyApi是接口可以Mock,但Mock框架接口时需要适配框架的特性:比如Retrofit的Call类型、Room的Flow异步返回。而RemoteDataSource的返回类型是业务相关的SomeResponse,Mock时只需返回固定业务数据,无需配置MockWebServer或测试数据库,测试代码更简洁,也能避免框架特性带来的测试复杂度。统一数据层抽象,适配多数据源差异
当项目存在多类数据源(比如同时有Retrofit接口、第三方SDK、SharedPreferences),用RemoteDataSource/LocalDataSource统一抽象后,Repository面对的是一致的调用风格。比如第三方SDK是回调式、SharedPreferences是同步读取,都可以在DataSource层统一转换成Repository需要的调用方式(比如suspend函数、Flow),Repository无需关心不同数据源的调用差异。
内容的提问来源于stack exchange,提问作者H.Kim

