[Android] Dependency Injection Part 1. Overview

[Android] Dependency Injection Part 1. Overview

이 글은 Android Developer κ°€μ΄λ“œ λ‚΄μš©μ„ ν† λŒ€λ‘œ μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 예제 μ½”λ“œλŠ” Kotlin만 κ°€μ Έμ™”μœΌλ©°, Java μ½”λ“œλŠ” μ›λ¬Έμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜μ‘΄μ„± μ£Όμž…(Dependency injection, DI)은 ν”„λ‘œκ·Έλž˜λ°μ—μ„œ 널리 μ‚¬μš©λ˜κ³ , Android κ°œλ°œμ— μ ν•©ν•œ κΈ°μˆ μž…λ‹ˆλ‹€. DI의 원칙을 λ”°λ₯΄λ©΄ 쒋은 μ•± μ•„ν‚€ν…μ²˜μ˜ κΈ°λ°˜μ„ κ°–μΆœ 수 μžˆμŠ΅λ‹ˆλ‹€.

DIλ₯Ό 톡해 얻을 수 μžˆλŠ” μž₯점

  • μ½”λ“œ μž¬μ‚¬μš© κ°€λŠ₯
  • λ¦¬νŒ©ν† λ§ 용이
  • ν…ŒμŠ€νŠΈ 용이

DI의 κΈ°λ³Έ κ°œλ…

Androidμ—μ„œμ˜ DIλ₯Ό μ‚΄νŽ΄λ³΄κΈ° 전에, 이 νŒŒνŠΈμ—μ„œλŠ” DI의 λ™μž‘ 방식에 λŒ€ν•œ μ’€ 더 기본적인 κ°œλ…μ„ μ„€λͺ…ν•©λ‹ˆλ‹€.

μ˜μ‘΄μ„± μ£Όμž…μ΄λž€?

ν•œ ν΄λž˜μŠ€μ—μ„œ λ‹€λ₯Έ 클래슀 μ°Έμ‘°λŠ” 자주 ν•„μš”ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Car ν΄λž˜μŠ€λŠ” Engine 클래슀λ₯Ό μ°Έμ‘°ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•„μš”ν•œ 클래슀λ₯Ό λ””νŽœλ˜μ‹œλΌκ³  ν•©λ‹ˆλ‹€. 그리고 이 μ˜ˆμ‹œμ—μ„œ Car ν΄λž˜μŠ€λŠ” μ‹€ν–‰μ‹œ Engine μΈμŠ€ν„΄μŠ€λ₯Ό κ°–λŠ” 것에 μ˜μ‘΄ν•©λ‹ˆλ‹€.

ν΄λž˜μŠ€μ— ν•„μš”ν•œ 객체λ₯Ό μ–»λŠ” 방법은 3가지가 μžˆμŠ΅λ‹ˆλ‹€:

  1. ν΄λž˜μŠ€κ°€ ν•„μš”ν•œ λ””νŽœλ˜μ‹œλ₯Ό μƒμ„±ν•œλ‹€. Car ν΄λž˜μŠ€κ°€ Engine μΈμŠ€ν„΄μŠ€λ₯Ό 직접 μƒμ„±ν•˜κ³  μ΄ˆκΈ°ν™”
  2. λ‹€λ₯Έ μ–΄λ”˜κ°€μ—μ„œ κ°€μ Έμ˜¨λ‹€. getContext()λ‚˜ getSystemService() 같은 λͺ‡λͺ‡ Android APIκ°€ μ΄λ ‡κ²Œ λ™μž‘ν•¨
  3. νŒŒλΌλ―Έν„°λ₯Ό 톡해 λ°›λŠ”λ‹€. ν΄λž˜μŠ€κ°€ 생성될 λ•Œ λ˜λŠ” 각 λ””νŽœλ˜μ‹œλ§ˆλ‹€ ν•„μš”ν•œ ν•¨μˆ˜μ˜ νŒŒλΌλ―Έν„°λ₯Ό 톡해 전달받을 수 μžˆλ‹€. Car μƒμ„±μžμ˜ νŒŒλΌλ―Έν„°λ‘œ Engine을 μ „λ‹¬λ°›λŠ”λ‹€.

μ„Έ 번째 방법이 λ°”λ‘œ DI μž…λ‹ˆλ‹€! 이 방법을 톡해 ν΄λž˜μŠ€μ— μΈμŠ€ν„΄μŠ€λ₯Ό ν¬ν•¨μ‹œν‚€μ§€ μ•Šκ³ λ„ μ˜μ‘΄μ„±μ„ μ–»κ±°λ‚˜ μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ•„λž˜λŠ” μ˜μ‘΄μ„± μ£Όμž…μ—†μ΄ Car ν΄λž˜μŠ€κ°€ Engine μΈμŠ€ν„΄μŠ€λ₯Ό 직접 μƒμ„±ν•˜λŠ” μ˜ˆμ‹œ μ½”λ“œμž…λ‹ˆλ‹€.


class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}


이 μ½”λ“œλŠ” μ•„λž˜μ˜ 이유둜 λ¬Έμ œκ°€ 될 수 μžˆλŠ” μ½”λ“œμž…λ‹ˆλ‹€.

  • Car와 Engine이 λ‹¨λ‹¨νžˆ μ—°κ²°λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€ - Car μΈμŠ€ν„΄μŠ€κ°€ Engineμ΄λΌλŠ” ν•œ 가지 νƒ€μž…μ„ μ‚¬μš©ν•˜μ—¬, μ„œλΈŒ ν΄λž˜μŠ€λ‚˜ λŒ€μ²΄ν•  κ΅¬ν˜„μ²΄λ₯Ό μ‰½κ²Œ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. Car μžμ²΄μ—μ„œ μ‚¬μš©ν•  Engine을 직접 μƒμ„±ν•˜λ―€λ‘œ, Gasλ‚˜ Electric 엔진 νƒ€μž…μ΄ μΆ”κ°€λ˜λ©΄ Carλ₯Ό μž¬μ‚¬μš©ν•  수 μ—†κ³  두 가지 Car νƒ€μž…μ„ μƒˆλ‘œ λ§Œλ“€μ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • Engine에 λŒ€ν•œ λ‹¨λ‹¨ν•œ μ˜μ‘΄μ„±μ€ ν…ŒμŠ€νŠΈλ„ λ”μš± μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€. Carκ°€ μ‹€μ œ Engine μΈμŠ€ν„΄μŠ€λ§Œ μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— μ—¬λŸ¬ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μœ„ν•œ Engine의 Mock 객체 μ‚¬μš©μ΄ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

μ˜μ‘΄μ„± μ£Όμž…μ„ μ‚¬μš©ν•˜λ©΄ μ½”λ“œκ°€ μ–΄λ–»κ²Œ λ³€κ²½λ κΉŒμš”? Car μΈμŠ€ν„΄μŠ€λ§ˆλ‹€ μ΄ˆκΈ°ν™” μ‹œμ μ— Engine을 μƒμ„±ν•˜μ§€ μ•Šκ³ , Car μƒμ„±μžμ˜ νŒŒλΌλ―Έν„°λ₯Ό 톡해 μ „λ‹¬λ°›μŠ΅λ‹ˆλ‹€.

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}


main ν•¨μˆ˜μ—μ„œ Carλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. Carκ°€ Engine에 μ˜μ‘΄ν•˜κΈ° λ•Œλ¬Έμ— Engine μΈμŠ€ν„΄μŠ€λ₯Ό λ¨Όμ € μƒμ„±ν•˜μ—¬ Car μΈμŠ€ν„΄μŠ€ 생성 μ‹œ μ‚¬μš©ν•©λ‹ˆλ‹€. DI 기반의 접근법은 μ•„λž˜μ˜ μž₯점을 κ°€μ§‘λ‹ˆλ‹€:

  • Car μž¬μ‚¬μš© κ°€λŠ₯ - Engine의 λ‹€λ₯Έ κ΅¬ν˜„μ²΄λ₯Ό Car에 전달할 수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, ElectricEngineμ΄λΌλŠ” Engine의 μƒˆλ‘œμš΄ μ„œλΈŒ 클래슀λ₯Ό μ •μ˜ν–ˆμ„ λ•Œ, DIλ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ Car 클래슀 변경없이 ElectricEngine만 Car에 μ „λ‹¬ν•˜λ©΄ λ©λ‹ˆλ‹€.
  • Car ν…ŒμŠ€νŠΈ 용이 - μ—¬λŸ¬ μ‹œλ‚˜λ¦¬μ˜€ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ 객체λ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, FakeEngineμ΄λΌλŠ” Engine의 ν…ŒμŠ€νŠΈμš© 객체λ₯Ό λ§Œλ“€μ–΄ 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ— 맞게 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Androidμ—μ„œ μ˜μ‘΄μ„± μ£Όμž… 방법은 크게 2가지가 μžˆμŠ΅λ‹ˆλ‹€:

  • μƒμ„±μž μ£Όμž…: μœ„ μ½”λ“œμ—μ„œ μ„€λͺ…ν•œ λ°©μ‹μœΌλ‘œ, λ””νŽœλ˜μ‹œλ₯Ό μƒμ„±μžμ— μ „λ‹¬ν•©λ‹ˆλ‹€.
  • ν•„λ“œ μ£Όμž…(λ˜λŠ” Setter μ£Όμž…): Activityλ‚˜ Fragment 같은 νŠΉμ • Android Framework ν΄λž˜μŠ€λ“€μ€ μ‹œμŠ€ν…œμ— μ˜ν•΄ μƒμ„±λ˜μ–΄, μƒμ„±μžλ₯Ό ν†΅ν•œ μ˜μ‘΄μ„± μ£Όμž…μ΄ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€. ν•„λ“œ μ£Όμž…μ„ μ‚¬μš©ν•˜λ©΄ 클래슀 생성 이후에 의쑴 관계λ₯Ό λ‚˜νƒ€λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œκ°€ ν•„λ“œ μ£Όμž… μ˜ˆμ‹œμž…λ‹ˆλ‹€.
class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}


DI μžλ™ν™”

μœ„μ—μ„œ λ³Έ DI 예제 μ½”λ“œλŠ” λͺ¨λ‘ λ‹€λ₯Έ ν΄λž˜μŠ€μ— λŒ€ν•œ 의쑴 관계λ₯Ό 직접 생성, 전달, κ΄€λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. Car ν΄λž˜μŠ€κ°€ μ§€κΈˆμ€ ν•˜λ‚˜μ˜ λ””νŽœλ˜μ‹œλ§Œ κ°–μ§€λ§Œ λ””νŽœλ˜μ‹œμ™€ ν΄λž˜μŠ€κ°€ λŠ˜μ–΄λ‚¨μ— 따라 λ³΅μž‘λ„ λ˜ν•œ μ¦κ°€ν•˜κ²Œ λ©λ‹ˆλ‹€.

규λͺ¨κ°€ 큰 μ•±μ˜ 경우, λͺ¨λ“  λ””νŽœλ˜μ‹œλ₯Ό μ–»μ–΄μ„œ μ•Œλ§žκ²Œ μ—°κ²°ν•˜κΈ° μœ„ν•œ λ§Žμ€ μ–‘μ˜ λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ μ½”λ“œκ°€ μƒκΈ°κ²Œ λ©λ‹ˆλ‹€. Multi-layered architecture의 경우, μƒμœ„ κ³„μΈ΅μ• μ„œ ν•„μš”ν•œ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ ν•˜μœ„μ˜ λͺ¨λ“  λ””νŽœλ˜μ‹œλ₯Ό 전달해야 ν•©λ‹ˆλ‹€.

κ·Έλž˜μ„œ λ””νŽœλ˜μ‹œλ₯Ό μƒμ„±ν•˜κ³  μ „λ‹¬ν•˜λŠ” 과정을 μžλ™ν™”ν•˜κΈ° μœ„ν•œ λΌμ΄λΈŒλŸ¬λ¦¬λ“€μ΄ μžˆμŠ΅λ‹ˆλ‹€. Java, Kotlin 그리고 Android의 μ˜μ‘΄μ„± μ£Όμž… λΌμ΄λΈŒλŸ¬λ¦¬λ‘œλŠ” Google의 Daggerκ°€ 많이 쓰이고 μžˆμŠ΅λ‹ˆλ‹€.

DI의 λŒ€μ•ˆ - Service Locator

DI λŒ€μ‹  Service Locator λ””μžμΈ νŒ¨ν„΄μ„ μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. Service Locator λ˜ν•œ ν΄λž˜μŠ€κ°„ λ‹¨λ‹¨ν•œ 연결고리λ₯Ό λΆ„λ¦¬ν•˜λŠ” 데 도움을 μ€λ‹ˆλ‹€. 보톡 ServiceLocatorλΌλŠ” 클래슀λ₯Ό λ§Œλ“€κ³ , λ””νŽœλ˜μ‹œλ₯Ό μƒμ„±ν•΄μ„œ μ €μž₯ν•΄λ’€λ‹€κ°€ ν•„μš”ν•œ 경우 μ „λ‹¬ν•˜λŠ” λ‘œμ§μ„ λ‹΄μŠ΅λ‹ˆλ‹€.

object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}


Service Locator νŒ¨ν„΄μ€ ν΄λž˜μŠ€κ°€ μ£Όλ„κΆŒμ„ 가지고 μ£Όμž…λ˜μ–΄μ•Ό ν•  클래슀λ₯Ό μš”μ²­ν•˜μ§€λ§Œ, DIλŠ” 앱이 μ£Όλ„κΆŒμ„ 가지고 ν•„μš”ν•œ 클래슀λ₯Ό 사전에 μ£Όμž…ν•©λ‹ˆλ‹€.

μ•±μ˜ 규λͺ¨μ— 따라 μ•Œλ§žμ€ 방법 μ„ νƒν•˜κΈ°

λ¨Όμ € ν”„λ‘œμ νŠΈμ˜ 규λͺ¨λŠ” μ–΄λ–»κ²Œ νŒŒμ•…ν• κΉŒμš”? μ—¬κΈ°μ„œλŠ” μ•±μ˜ μ‚¬μ΄μ¦ˆκ°€ 큰가 μž‘μ€κ°€λ₯Ό ν™”λ©΄ 개수둜 μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ¬Όλ‘  ν™”λ©΄μ˜ κ°œμˆ˜λŠ” μ•±μ˜ 크기λ₯Ό κ²°μ •ν•˜λŠ” λ§Žμ€ μš”μ†Œ 쀑 ν•˜λ‚˜μΌ λΏμž…λ‹ˆλ‹€.

ν”„λ‘œμ νŠΈ 크기 μž‘λ‹€ 쀑간 크닀
ν™”λ©΄ 개수 1-3 4-7 8+
  • 직접 μ˜μ‘΄μ„± μ£Όμž…: ν™•μž₯성이 쒋지 μ•ŠμœΌλ―€λ‘œ 비ꡐ적 μž‘μ€ μ•±μ—λ§Œ μ ν•©ν•©λ‹ˆλ‹€. ν”„λ‘œμ νŠΈ 크기가 컀질수둝 λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ μ½”λ“œλ„ μ¦κ°€ν•©λ‹ˆλ‹€.
  • Service Locator: λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ μ½”λ“œκ°€ 비ꡐ적 μ μ§€λ§Œ ν™•μž₯성이 쒋지 μ•ŠμŠ΅λ‹ˆλ‹€. 싱글톀 객체에 μ˜μ‘΄ν•˜λ―€λ‘œ, ν…ŒμŠ€νŠΈ λ˜ν•œ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.
  • Dagger: ν™•μž₯성이 μ’‹κ³ , λ³΅μž‘ν•œ 앱에도 μ ν•©ν•©λ‹ˆλ‹€.
ν”„λ‘œμ νŠΈ 크기 μž‘λ‹€ 쀑간 크닀
μΆ”μ²œν•˜λŠ” 방법 μˆ˜λ™ DI
Service Locator
Dagger
Dagger Dagger

ν˜„μž¬λŠ” μž‘μ€ 앱인데 규λͺ¨κ°€ 컀질 κ²ƒμœΌλ‘œ μ˜ˆμƒλœλ‹€λ©΄, 더 λ§Žμ€ μ½”λ“œκ°€ λ³€κ²½λ˜κΈ° 전에 미리 Dagger둜 μ „ν™˜ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

라이브러리 규λͺ¨μ— 따라 μ•Œλ§žμ€ 방법 μ„ νƒν•˜κΈ°

SDKλ‚˜ 라이브러리λ₯Ό κ°œλ°œν•˜κ³  μžˆλ‹€λ©΄ μˆ˜λ™ DI λ˜λŠ” 규λͺ¨μ— 따라 Daggerλ₯Ό μ‚¬μš©ν•˜μ„Έμš”. λ‹€λ§Œ μ˜μ‘΄μ„± μ£Όμž…μ„ μœ„ν•΄ 3rd party 라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ 라이브러리의 크기 λ˜ν•œ 증가할 수 μžˆλ‹€λŠ” 점은 μ£Όμ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

κ²°λ‘ 

DIλ₯Ό μ‚¬μš©ν•˜λ©΄ μ•„λž˜μ™€ 같은 μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.

  • μ½”λ“œ μž¬μ‚¬μš© κ°€λŠ₯ 및 μ˜μ‘΄μ„± 뢄리: κ΅¬ν˜„μ²΄λ₯Ό λ³€κ²½ν•˜κΈ° μ‰¬μ›Œμ§‘λ‹ˆλ‹€. ν΄λž˜μŠ€κ°€ 더이상 λ””νŽœλ˜μ‹œλ“€μ΄ μ–΄λ–»κ²Œ μƒμ„±λ˜λŠ”μ§€ 신경쓰지 μ•Šμ•„λ„ 되고, μ œμ–΄μ˜ μ—­μ „ 덕뢄에 μ½”λ“œμ˜ μž¬μ‚¬μš©μ„±μ΄ μ¦κ°€ν•©λ‹ˆλ‹€.
  • λ¦¬νŒ©ν† λ§ 용이: 의쑴 관계가 API에 λΆ„λͺ…νžˆ λ“œλŸ¬λ‚˜κΈ° λ•Œλ¬Έμ—, κ΅¬ν˜„μ²΄μ˜ μˆ¨κ²¨μ§„ λ‚΄μš©μ„ 보지 μ•Šκ³  객체 생성 μ‹œμ  λ˜λŠ” 컴파일 μ‹œμ μ—λ§Œ ν™•μΈν•˜λ©΄ λ©λ‹ˆλ‹€.
  • ν…ŒμŠ€νŒ… 용이: ν΄λž˜μŠ€κ°€ μžμ‹ μ˜ 의쑴 관계λ₯Ό κ΄€λ¦¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, ν…ŒμŠ€νŠΈ μ‹œ 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ— λ§žλŠ” κ΅¬ν˜„μ²΄λ₯Ό μ „λ‹¬ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€.

DI의 μž₯점을 μ™„λ²½ν•˜κ²Œ μ΄ν•΄ν•˜λ €λ©΄ 앱에 직접 μ¨λ³΄λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. λ‹€μŒ νŒŒνŠΈμ—μ„œ 직접 μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜λŠ” λ°©λ²•μœΌλ‘œ λŒμ•„μ˜€κ² μŠ΅λ‹ˆλ‹€!

Comments