Retrofit以object形式创建及AppModule继承BaseApi的技术疑问
我开发了一个使用Retrofit和Hilt的MVVM程序,有两个技术疑问:
- 为何多数示例中Retrofit以object形式而非class形式创建?
- 为何不应让AppModule继承BaseApi来限制直接访问Retrofit?
我习惯按此方式编写代码,但从未见过同类实现,希望了解专家对该写法存在问题的看法。
Retrofit类:
open class BaseApi protected constructor(){ private fun createBuilder(baseUrl : String): Retrofit { val okHttpClient = OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .build() return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() } protected fun getWeatherService(): WeatherService { val weatherServiceBaseAddress = "https://api.weatherapi.com/" return createBuilder(weatherServiceBaseAddress).create(WeatherService::class.java) } }AppModule:
@Module @InstallIn(SingletonComponent::class) class AppModule : BaseApi() { @Singleton @Provides fun weatherServiceProvider() = getWeatherService() }
1. 为何多数示例用object创建Retrofit而非class?
Retrofit实例是无状态的,它的核心作用是基于配置生成Service接口代理,一旦初始化完成就不需要修改。用object(单例)实现的核心原因:
- 减少资源浪费:每次创建Retrofit都会初始化OkHttpClient、解析Converter等,重复创建会消耗额外内存和CPU,全局单例只需要初始化一次
- 简化使用:无需手动管理实例生命周期,直接通过
object的静态方式访问,不用考虑初始化时机 - 符合设计逻辑:绝大多数场景下,App只需要一套Retrofit配置(统一的超时、拦截器、Converter),单例刚好匹配这种全局统一的需求
当然,如果你的App需要多套不同配置的Retrofit(比如不同baseUrl、不同认证逻辑),那用class创建并通过DI容器管理会更灵活,但这种场景相对少见,所以多数示例用object。
2. AppModule继承BaseApi的问题
你的写法虽然能运行,但从架构设计和Hilt的最佳实践来看,存在几个关键问题:
(1)违反单一职责原则
Hilt的@Module类的唯一职责是声明依赖提供规则,专注于告诉Hilt如何创建和注入对象。而BaseApi是负责构建Retrofit和Service的工具类,两者职责完全无关。强行继承会让AppModule的职责混乱,后续维护时很难区分哪些是Module的核心逻辑,哪些是继承来的Api构建逻辑。
(2)破坏Hilt的依赖管理逻辑
Hilt的Module实例是由框架自身管理的,让它继承工具类会把工具类的生命周期和Module绑定在一起:
- 会提前触发BaseApi的初始化,哪怕暂时不需要创建WeatherService,不符合延迟加载的原则
- 如果后续需要添加其他Api Service,要么往BaseApi里塞更多方法(让它变成大杂烩),要么尝试多继承(Kotlin不支持),扩展性极差
(3)封装性的实现方式不合理
你想通过protected方法限制直接访问Retrofit,其实更合理的方式是把BaseApi作为依赖注入到Module中,而不是继承。这样既分离了职责,又保证了封装性:
// 调整BaseApi为普通类,去掉protected构造 class BaseApi { private fun createBuilder(baseUrl: String): Retrofit { val okHttpClient = OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .build() return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() } // 改为public方法,通过依赖注入提供访问 fun getWeatherService(): WeatherService { val weatherServiceBaseAddress = "https://api.weatherapi.com/" return createBuilder(weatherServiceBaseAddress).create(WeatherService::class.java) } } @Module @InstallIn(SingletonComponent::class) class AppModule { // 提供BaseApi实例 @Singleton @Provides fun provideBaseApi(): BaseApi = BaseApi() // 通过BaseApi获取WeatherService @Singleton @Provides fun provideWeatherService(baseApi: BaseApi): WeatherService = baseApi.getWeatherService() }
这种方式下,Retrofit依然不会被直接访问,同时Module和BaseApi各司其职,后续扩展其他Service也很方便——只需要在BaseApi里添加对应方法,再在Module里新增Provider即可。
内容的提问来源于stack exchange,提问作者Ali Doran

