← Назад к вопросам

Какие знаешь причины, по которым нельзя полностью отказаться от @Provides и использовать только @Binds?

2.4 Senior🔥 101 комментариев
#Dependency Injection#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

@Provides vs @Binds: Почему нельзя полностью отказаться от @Provides

Хотя @Binds является более эффективной и рекомендуемой альтернативой для предоставления абстракций через их реализации, полностью отказаться от @Provides невозможно по нескольким фундаментальным причинам. Основное различие заключается в том, что @Binds — это абстрактный метод, который лишь указывает связь, в то время как @Provides — это конкретный метод, содержащий логику создания объекта.

Основные причины необходимости @Provides

1. Создание объектов с нетривиальной логикой инициализации

@Binds может только связать интерфейс с конкретной реализацией, но не может содержать код создания объекта. Когда объект требует сложной инициализации, конфигурации или условной логики, необходим @Provides:

@Module
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(
        authInterceptor: AuthInterceptor,
        loggingInterceptor: HttpLoggingInterceptor
    ): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(authInterceptor)
            .addInterceptor(loggingInterceptor)
            .build()
    }
}

2. Работа с внешними библиотеками и классами без контроля

Когда нужно внедрить класс из сторонней библиотеки, которую вы не контролируете и не можете изменить, @Provides — единственный вариант:

@Module
object DateTimeModule {
    @Provides
    fun provideDateTimeFormatter(): DateTimeFormatter {
        return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
    }
    
    @Provides
    @Singleton
    fun provideGson(): Gson {
        return GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
            .create()
    }
}

3. Создание объектов, зависящих от параметров времени выполнения

Хотя Dagger в основном работает с зависимостями, известными на этапе компиляции, иногда необходимо создавать объекты на основе параметров, известных только во время выполнения:

@Module
class DatabaseModule(private val context: Context) {
    @Provides
    @Singleton
    fun provideAppDatabase(): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app-database"
        ).build()
    }
}

4. Предоставление примитивных типов и аннотированных зависимостей

Когда нужно предоставить строки, числа или другие примитивные типы с квалификаторами:

@Module
object ConfigModule {
    @Provides
    @Named("BaseUrl")
    fun provideBaseUrl(): String = "https://api.example.com"
    
    @Provides
    @ApiTimeout
    fun provideTimeoutSeconds(): Long = 30L
}

5. Условное предоставление зависимостей

В случаях, когда реализация должна выбираться динамически на основе условий:

@Module
object AnalyticsModule {
    @Provides
    @Singleton
    fun provideAnalyticsTracker(
        @Named("buildType") buildType: String
    ): AnalyticsTracker {
        return when (buildType) {
            "debug" -> DebugAnalyticsTracker()
            "release" -> FirebaseAnalyticsTracker()
            else -> StubAnalyticsTracker()
        }
    }
}

6. Создание объектов с помощью builder-паттерна или фабричных методов

Многие библиотеки используют builder-паттерн, который требует последовательности вызовов методов:

@Module
object RetrofitModule {
    @Provides
    @Singleton
    fun provideRetrofit(
        okHttpClient: OkHttpClient,
        @Named("BaseUrl") baseUrl: String
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
    }
}

Сравнительная таблица возможностей

Возможность@Binds@Provides
Связь интерфейса с реализацией✅ Да✅ Да
Содержит логику создания❌ Нет✅ Да
Может создавать новые объекты❌ Нет✅ Да
Работа со сторонними классами❌ Нет✅ Да
Условная логика❌ Нет✅ Да
Вызов builder-методов❌ Нет✅ Да
Производительность (время компиляции)✅ Выше❌ Ниже

Практические рекомендации

  1. Используйте @Binds когда возможно — для простых связей интерфейс-реализация
  2. Используйте @Provides когда нужно — для сложного создания объектов
  3. Комбинируйте оба подхода в одном модуле при необходимости:
@Module
abstract class RepositoryModule {
    @Binds
    abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
    
    @Binds
    abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository
}

@Module
class CompanionModule {
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

Заключение

Полный отказ от @Provides в пользу @Binds невозможен, так как они решают разные задачи. @Binds оптимизирован для случаев, когда нужно лишь указать связь между абстракцией и реализацией, что улучшает производительность компиляции и читаемость кода. Однако @Provides остается незаменимым для всех сценариев, требуюших:

  • Создания объектов с нетривиальной логикой
  • Работы со сторонними библиотеками
  • Конфигурации объектов на этапе выполнения
  • Предоставления примитивных типов
  • Условного создания зависимостей

Оптимальная стратегия — использовать @Binds везде, где это возможно (для простых связей), и @Provides там, где необходима логика создания объектов. Такой подход сочетает преимущества производительности @Binds с гибкостью @Provides.

Какие знаешь причины, по которым нельзя полностью отказаться от @Provides и использовать только @Binds? | PrepBro