기록

[GPT가 알려주는] 의존성 주입(DI: Dependency Injection)이란? 본문

잡동사니/GPT가 알려주는

[GPT가 알려주는] 의존성 주입(DI: Dependency Injection)이란?

heylo 2025. 7. 2. 14:04

🚪 1. 의존성 주입(DI: Dependency Injection)이란?

❓ 의존성이 뭐야?

  • 어떤 클래스가 다른 클래스(객체)를 필요로 한다면, 그게 바로 "의존"하는 거예요.
class Car {
    val engine = Engine()
}
  • Car는 Engine을 직접 생성해서 사용하고 있어요.
  • 이러면 나중에 Engine을 테스트용으로 바꾸거나, 다른 종류의 엔진을 넣고 싶을 때 불편해요.

 

✅ DI는?

  • 필요한 객체(Engine)를 직접 만들지 않고, 외부에서 주입받는 방식이에요.
class Car(val engine: Engine)

이제 Car는 엔진을 받기만 해요. 만드는 책임은 외부(DI 컨테이너)가 지는 거죠.

 

🧠 2. 그럼 왜 필요해? 장점은?

  • 테스트 편함: Mock 객체를 쉽게 넣을 수 있어요.
  • 유연성 증가: 어떤 구현체를 넣을지 쉽게 바꿀 수 있어요.
  • 코드 간결화: 복잡한 생성 로직을 밖으로 뺄 수 있어요.

 

 

🧰 3. Dagger는 뭔가요?

  • Dagger는 **DI를 자동으로 해주는 도구(라이브러리)**예요.
  • 직접 객체 만들고 연결하던 걸, Dagger가 해줘요.
  • 컴파일 타임에 다 처리해서 성능이 좋아요.

 

🌿 4. 그럼 Hilt는 뭐야?

  • Hilt는 Dagger를 쉽게 쓸 수 있게 래핑한 라이브러리예요.
  • Android 전용! 라이프사이클까지 관리해줘요.
  • 그냥 "Dagger의 쉬운 버전"이라고 보면 돼요.

 

👀 5. 코드 예시로 이해해보자

1. 주입될 객체 (예: 엔진)

class Engine @Inject constructor()

 

2. 의존성을 가지는 클래스 (예: 자동차)

class Car @Inject constructor(val engine: Engine)

 

3. Hilt Application

@HiltAndroidApp
class MyApp : Application()

 

4. Hilt 주입 받는 곳 (예: Activity)

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var car: Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // car가 자동으로 주입돼요!
    }
}

 

💡 한 줄 요약

DI는 객체를 직접 만들지 않고 외부에서 넣어주는 방식,
Dagger는 DI를 자동으로 처리하는 도구,
Hilt는 Dagger를 Android에서 쉽게 쓰게 해주는 도구!

 

질문
DI는 객체를 parameter로 넘기는 것과 같나요? 네, 맞습니다! 객체를 외부에서 만들어서 넘기는 개념이에요.
그럼 DI 프레임워크는 뭘 해주는 건가요? 그 객체를 대신 생성해서, 알아서 주입해줘요. (자동화)

 

 

✅ 1. 테스트에서 왜 DI가 좋은가?

DI를 사용하면 클래스 안에서 직접 객체를 만들지 않고, 외부에서 주입하기 때문에,
테스트 시에 가짜(Mock) 객체를 쉽게 넣을 수 있어요.

📌 예를 들어 볼게요:

직접 생성하는 방식 (DI ❌)

class Car {
    private val engine = Engine()  // 테스트할 수 없음
}

 

  • 테스트할 때, Engine을 다르게 바꿀 수 없음
  • Engine이 무겁거나, 서버 통신하면 테스트 어려움

 

 

주입받는 방식 (DI ✅)

class Car(private val engine: Engine)

 

테스트에서는 이렇게:

 
val mockEngine = MockEngine()
val testCar = Car(mockEngine)

 

  • ✅ 진짜 Engine을 안 써도 되고
  • ✅ 테스트용 Engine으로 빠르게 검증 가능
  • ✅ 외부 API, DB 없이 테스트 가능 (테스트가 "순수"함)

 

🧪 요약

항목 직접 생성 DI
테스트 유연성 ❌ 낮음 ✅ 높음
Mock 객체 사용 ❌ 어려움 ✅ 쉬움
단위 테스트 속도 ❌ 느림 ✅ 빠름

 

 

 

✅ 2. Hilt의 전체 구조 흐름

이제 Hilt가 어떻게 구성되고 동작하는지 전체 흐름을 그림처럼 정리해드릴게요.

🎯 Hilt 구조 요약

[ @HiltAndroidApp ]
        ↓
[ Application 클래스 ]  ← 앱 전역 DI 컨테이너
        ↓
[ @AndroidEntryPoint ]
        ↓
[ Activity / Fragment / ViewModel ] ← 자동으로 주입받음
        ↓
[ @Inject constructor ] ← 생성자에 객체 주입
        ↓
[ @Module + @Provides / @Binds ] ← 커스터마이징 필요한 경우

 

🔧 주요 구성요소


구성요소 설명
@HiltAndroidApp Hilt의 시작점. Application에 붙이면 Hilt가 앱 전체를 관리
@AndroidEntryPoint 주입이 필요한 클래스(Activity 등)에 붙임
@Inject 객체를 생성하고 싶을 때 사용. 생성자 주입
@Module 복잡한 객체 제공할 때 수동으로 등록
@Provides / @Binds 객체 생성 로직을 커스터마이징할 때

 

 

🧩 예제 흐름

// 1. 앱 시작
@HiltAndroidApp
class MyApp : Application()

// 2. 주입 받는 대상
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var car: Car
}

// 3. 객체 정의
class Car @Inject constructor(val engine: Engine)

// 4. 필요 시 Module 정의
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    fun provideEngine(): Engine = TurboEngine()
}

 

📌 Hilt는 이렇게 도와줘요

  • 필요한 객체를 @Inject로 자동 생성
  • Activity/Fragment/ViewModel 등에 자동 주입
  • Lifecycle 따라 필요한 범위(Singleton, Activity 등)로 관리
  • 테스트에서도 @TestInstallIn, @UninstallModules로 커스터마이징 가능

 

✅ 정리


질문
DI가 테스트에 왜 유리해요? 필요한 의존성을 외부에서 주입받기 때문에,
가짜(Mock) 객체를 넣어서
빠르고 독립적인 테스트가 가능해요.
Hilt의 구조는 어떻게 돼요? Application → EntryPoint → Inject → Module/Provides 구조로,
전체 앱의 의존성을 자동으로 관리해줘요.