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

Можно ли создать Dagger модуль в котором будут два метода которые возвращают одинаковый тип данных?

2.2 Middle🔥 202 комментариев
#Dependency Injection

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

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

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

Можно ли создать модуль Dagger с двумя методами, возвращающими одинаковый тип?

Да, это возможно, но с критически важными оговорками. В Dagger 2 модуль может содержать несколько методов @Provides, возвращающих одинаковый тип, но вы должны явно разрешить конфликт привязок, иначе компиляция завершится с ошибкой [Dagger/DuplicateBindings]. Dagger требует однозначности для каждой зависимости во время компиляции.

Почему это вызывает проблему?

Dagger строит граф зависимостей, где каждый ключ (тип + квалификатор) должен иметь ровно одну привязку. Два метода, возвращающих String без квалификаторов, создают два разных источника для одного ключа, что приводит к неоднозначности.

Решения конфликта

1. Использование @Named или пользовательских @Qualifier

Самый частый способ. Вы создаете квалификаторы — аннотации, которые "маркируют" зависимости, делая ключи уникальными.

import javax.inject.Named
import dagger.Module
import dagger.Provides

@Module
class AppModule {
    @Provides
    @Named("ApiKey")
    fun provideApiKey(): String {
        return "secret_api_key_123"
    }

    @Provides
    @Named("BaseUrl")
    fun provideBaseUrl(): String {
        return "https://api.example.com/"
    }
}

Или с собственным квалификатором:

import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApiKey

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BaseUrl

@Module
class AppModule {
    @Provides
    @ApiKey
    fun provideApiKey(): String { ... }

    @Provides
    @BaseUrl
    fun provideBaseUrl(): String { ... }
}

Затем при инъекции указываете, какую именно реализацию нужно использовать:

class MyRepository @Inject constructor(
    @Named("ApiKey") private val apiKey: String,
    @Named("BaseUrl") private val baseUrl: String
)

2. Объединение в @Binds или предоставление через интерфейс

Если типы — реализации одного интерфейса, можно использовать @Binds в абстрактном модуле.

interface DataSource

class LocalDataSource : DataSource
class RemoteDataSource : DataSource

@Module
abstract class DataSourceModule {
    @Binds
    abstract fun bindLocal(local: LocalDataSource): DataSource
    // Но так нельзя! Тоже будет конфликт. Нужны квалификаторы:

    @Binds
    @Named("Local")
    abstract fun bindLocal(local: LocalDataSource): DataSource

    @Binds
    @Named("Remote")
    abstract fun bindRemote(remote: RemoteDataSource): DataSource
}

3. Предоставление разных экземпляров в зависимости от контекста

Иногда логика создания отличается, но тип тот же.

@Module
class NetworkModule {
    @Provides
    @Named("OkHttp")
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder().build()
    }

    @Provides
    @Named("LoggingOkHttp")
    fun provideLoggingOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor())
            .build()
    }
}

4. Использование @IntoSet, @IntoMap или @ElementsIntoSet

Если вам нужна коллекция всех объектов данного типа, Dagger позволяет собрать их в Set или Map.

@Module
class PluginModule {
    @Provides
    @IntoSet
    fun providePluginA(): Plugin = PluginA()

    @Provides
    @IntoSet
    fun providePluginB(): Plugin = PluginB()
    // Теперь можно инжектить Set<Plugin>
}

Что произойдет без квалификаторов?

Компиляция упадет с ошибкой, например:

error: [Dagger/DuplicateBindings] java.lang.String is bound multiple times

Практический пример с двумя Retrofit-клиентами

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthRetrofit

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class PublicRetrofit

@Module
class NetworkModule {
    @Provides
    @AuthRetrofit
    fun provideAuthRetrofit(
        @Named("AuthBaseUrl") baseUrl: String,
        authInterceptor: AuthInterceptor
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(OkHttpClient.Builder()
                .addInterceptor(authInterceptor)
                .build())
            .build()
    }

    @Provides
    @PublicRetrofit
    fun providePublicRetrofit(
        @Named("PublicBaseUrl") baseUrl: String
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .build()
    }
}

class MyService @Inject constructor(
    @AuthRetrofit private val authRetrofit: Retrofit,
    @PublicRetrofit private val publicRetrofit: Retrofit
) {
    // Используем разные клиенты для разных API
}

Итог

  1. Создать модуль с двумя методами одинакового возвращаемого типа — можно.
  2. Без квалификаторов — нельзя, Dagger выдаст ошибку компиляции.
  3. Основные решения: @Named, кастомные @Qualifier, @IntoSet/Map.
  4. Квалификаторы делают граф зависимостей явным и безопасным, что является одним из преимуществ Dagger перед другими DI-фреймворками.

Это поведение соответствует принципу явности и детерминированности Dagger: все зависимости должны быть однозначно разрешимы на этапе компиляции, что исключает runtime-ошибки из-за неоднозначности внедрения.

Можно ли создать Dagger модуль в котором будут два метода которые возвращают одинаковый тип данных? | PrepBro