이전에 사이드 프로젝트를 진행하면서 의존성 주입을 할 때 어떤 곳에는 @Provides를 사용하고, 어떤 곳에는 @Binds를 사용하는 것을 보고 그 차이가 궁금했습니다.
당시에는 명확하게 이해하지 못한 채 사용했었지만, 학습을 진행하며 그 차이를 조금씩 이해하게 되었습니다.
이에 대한 내용을 정리하여 보다 명확하게 개념을 정리하고자 합니다.
@Provides와 @Binds의 핵심 차이점
구분 | 사용 방식 | 특징 |
@Provides | 메서드 내에서 인스턴스를 반환 | 인터페이스뿐만 아니라 일반 클래스 객체도 생성 가능 |
@Binds | 구현체를 바인딩하는 방식 | 인터페이스와 구현체 간의 관계를 선언적으로 설정 |
@Provides를 사용하는 경우
@Provides는 Hilt가 직접 @Inject를 붙여 생성할 수 없는 경우 사용해야 합니다.
사용하는 경우:
- 외부 라이브러리 객체 (Retrofit, Room, OkHttp 등) 제공
- 객체 생성 로직이 필요한 경우 (조건문, 빌더 패턴, 팩토리 패턴 등)
- 객체를 생성하는 데 매개변수가 필요한 경우
@Provides 예제 (클래스 인스턴스를 반환)
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.build()
}
}
- Retrofit은 외부 라이브러리이므로, @Inject constructor를 사용할 수 없습니다.
- 따라서 직접 Retrofit 객체를 생성해서 Hilt에 제공해야 하므로 @Provides를 사용합니다.
@Binds를 사용하는 경우
@Binds는 Hilt가 @Inject로 직접 생성할 수 있는 구현체를 인터페이스와 연결할 때 사용해야 합니다.
사용하는 경우:
- 인터페이스와 구현체를 1:1로 연결할 때
- Hilt가 직접 생성할 수 있는 경우 (@Inject constructor 사용 가능)
- 불필요한 객체 생성을 피하고, 선언적으로 의존성을 바인딩하고 싶을 때
@Binds 예제 (인터페이스의 구현체를 바인딩)
interface ExampleRepository {
fun fetchData(): String
}
class ExampleRepositoryImpl @Inject constructor() : ExampleRepository {
override fun fetchData(): String = "Hello from Repository"
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindExampleRepository(
impl: ExampleRepositoryImpl
): ExampleRepository
}
- ExampleRepositoryImpl은 @Inject constructor가 있으므로 Hilt가 직접 생성할 수 있습니다.
- 따라서 @Provides를 사용할 필요 없이 @Binds로 인터페이스와 구현체만 연결하면 됩니다.
- @Binds를 사용하면 불필요한 객체 생성 로직 없이 더 간결한 코드가 됩니다.
@Provides와 @Binds의 동작 방식 차이점
@Provides 방식
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
fun provideExampleRepository(impl: ExampleRepositoryImpl): ExampleRepository {
return impl
}
}
- Hilt가 ExampleRepositoryImpl을 생성 (@Inject constructor가 있어야 함)
- @Provides 메서드의 매개변수로 impl: ExampleRepositoryImpl이 주입됨
- impl을 ExampleRepository 타입으로 반환하여 Hilt의 의존성 그래프에 추가
- ExampleRepository가 필요한 곳에서 이 구현체(ExampleRepositoryImpl)가 주입됨
즉, @Provides는 Hilt 그래프에서 ExampleRepositoryImpl을 받아와서, 반환값을 ExampleRepository 타입으로 제공하는 역할을 합니다.
@Binds 방식
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindExampleRepository(
impl: ExampleRepositoryImpl
): ExampleRepository
}
- Hilt가 ExampleRepositoryImpl을 생성 (@Inject constructor가 있어야 함)
- @Binds는 새로운 객체를 만들지 않고, Hilt에게 "ExampleRepository는 ExampleRepositoryImpl을 사용해야 한다"라고 선언함
- ExampleRepository가 필요한 곳에서 ExampleRepositoryImpl이 주입됨
즉, @Binds는 Hilt에게 ExampleRepositoryImpl이 ExampleRepository의 구현체임을 "연결"하는 역할만 수행합니다.
- 객체를 생성하지 않기 때문에 @Binds를 사용하는 것이 더 효율적입니다.
최종 정리
사용 상황 | @Provides | @Binds |
객체를 직접 생성해야 하는 경우 | ✅ | ❌ |
외부 라이브러리(Retrofit, Room 등) 사용 | ✅ | ❌ |
인터페이스와 구현체를 연결하는 경우 | 가능하지만 비효율적 | ✅ |
Hilt가 직접 객체를 생성할 수 있는 경우 | ❌ | ✅ |
결론
- 외부 라이브러리처럼 @Inject를 붙일 수 없는 경우 → @Provides 사용
- 구현체가 @Inject로 이미 Hilt에 등록되어 있고, 인터페이스와 연결만 하면 되는 경우 → @Binds 사용
- 객체를 직접 생성해야 하거나, 생성 과정이 필요한 경우 → @Provides 사용
- Hilt가 생성할 수 있는 경우에는 @Binds를 사용하여 불필요한 객체 생성을 줄이는 것이 좋습니다.
반응형
'Android > Hilt' 카테고리의 다른 글
[Android/Hilt] @Qualifier와 @Named 이해하기: 같은 타입의 여러 의존성을 구별하여 주입 (0) | 2025.03.07 |
---|---|
[Android/Hilt] @Inject와 @Module, @Provides 이해하기 (0) | 2025.03.07 |
[Android] Hilt를 활용한 Android 의존성 주입: @HiltAndroidApp으로 애플리케이션 컨테이너 설정하기 (0) | 2024.05.21 |