깨알 개념/기타

의존성 주입 (Dependency Injection)

interfacer_han 2024. 6. 20. 00:05

#1 의존성 주입(Dependency Injection)이란?

#1-1 Dependent와 Dependency

fun main() {
    val car = Car()
    car.startCar()
}

class Car {
    private val engine = Engine()
    private val airbag = Airbag()
    private val battery = Battery()

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

Car 클래스는 Engine, Battery, Airbag 클래스에 의존(= Car가 없으면 자립 불가)하고 있다. 이 관계에서 Car는 Engineㆍ Airbag Battery의 Dependent라고 부르며, Engineㆍ AirbagBattery는 Car의 Dependency라고 부른다.
 
전체 소스 코드

더보기
/* ~ 실행 방법 ~
 * 1. https://play.kotlinlang.org에 접속
 * 2. 여기에 있는 코드 복사
 * 3. 접속한 페이지에 붙여 넣기
 * 4. 접속한 페이지에 있는 Run 버튼 클릭
 *
 * ~ 실행 결과 미리 보기 ~
 * Crankshaft is ready
 * Cylinder is ready
 * Piston is ready
 * Engine is ready
 * Airbag is ready
 * Battery is ready
 * Car is ready
 */

fun main() {
    val car = Car()
    car.startCar()
}

class Car {
    private val engine = Engine()
    private val airbag = Airbag()
    private val battery = Battery()

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

class Engine {
    private val piston = Piston()

    fun startEngine() {
        piston.startPiston()
        println("${this::class.simpleName} is ready")
    }
}

class Airbag {
    fun startAirbag() {
        println("${this::class.simpleName} is ready")
    }
}

class Battery {
    fun startBattery() {
        println("${this::class.simpleName} is ready")
    }
}

class Piston {
    private val crankshaft = Crankshaft()
    private val cylinder = Cylinder()

    fun startPiston() {
        crankshaft.startCrankshaft()
        cylinder.startCylinder()
        println("${this::class.simpleName} is ready")
    }
}

class Crankshaft {
    fun startCrankshaft() {
        println("${this::class.simpleName} is ready")
    }
}

class Cylinder {
    fun startCylinder() {
        println("${this::class.simpleName} is ready")
    }
}

 

#1-2 간접적 Dependency

class Engine {
    private val piston = Piston()

    fun startEngine() {
        piston.startPiston()
        println("${this::class.simpleName} is ready")
    }
}

이번엔 Engine 클래스를 살펴본다. Engine은 Pistone 클래스의 Dependent, Pistone은 Engine의 Dependency다. 그렇다면, Car와 Piston의 관계는 어떨까? 서로가 서로에게 직접적인 Dependent 및 Dependency는 아니지만, 간접적인 Dependent 및 Dependency라고는 볼 수 있을 것이다.
 

#1-3 전체 의존성 도식도

본 게시글에서 다룰 클래스들의 의존성을 도식표로 표현하면 위와 같다. 화살표는 클래스 간의 종속성을 나타낸다. 예를 들어, Engine은 Piston에 종속된다. 종속의 사전적 의미는 '자주성이 없이 주가 되는 것에 딸려 붙음'이다. 종속은 '알아야 한다'라는 말로도 표현할 수 있다. 따라서 Engine은 Piston에 대해 알아야 한다. 반면, Piston은 Engine을 몰라도 된다. Piston을 설계할 땐 Engine에서 뭘 어떻게 할지 전혀 신경쓰지 않아도 된다는 것이다 (대신, Piston은 Crankshaft와 Cylinder에 대해 종속적이므로 Crankshaft와 Cylinder를 참조하며 설계해야 한다). Engine를 설계할 땐 Piston을 알아야 한다. '알아야 하는 쪽'에서 '몰라도 되는 쪽'으로 화살표를 이은 것이 위 도식도다.
 

#1-4 의존성 주입

위에 있는 Car, Engine, Piston의 예시처럼 의존성이 '밀접하게 결합'(= 각 클래스 내부에 또 다른 클래스가 정의됨)되어 있으면 테스트, 버그 수정, 코드 확장이 매우 어렵다. 이는 각 클래스끼리는 느슨하게 결합되면서, 클래스 하나하나의 내부에선 높은 응집력을 지닌다라는 객체 지향의 기본 철칙을 져버리는 패턴이기도 하다. 이 패턴을 해결하기 위해서, 의존성 주입(Dependency Injection)이라는 새로운 패턴이 등장했다.
 
Dependency 즉, Car 입장에서의 Dependency였던 Engine을 Car 클래스에 주입(Injection)하고 Engine 입장에서의 Dependency였던 Piston을 Engine 클래스에 주입(Injection)하는 것. 이것이 의존성 주입이다. '주입'이라는 단어에서 알 수 있듯이, 외부에서 내부로 무언가를 넣는다는 이미지 그대로의 개념이 맞다. 어렵게 생각할 것 없이, 클래스 내부가 아니라 외부에 Denpendency를 옮겨다놓으면 그게 의존성 주입이다. 아래 코드를 보자.
 

#2 의존성 주입의 3가지 구현 방식

#2-1 Constructor Injection (생성자 주입)

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston(crankshaft, cylinder)
    val engine = Engine(piston)
    val airbag = Airbag()
    val battery = Battery()

    val car = Car(engine, airbag, battery)
    car.startCar()
}

class Car(
    private val engine: Engine,
    private val airbag: Airbag,
    private val battery: Battery
) {
    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

생성자는 클래스에 외부로부터 무언가를 전달해줄 수 있는 대표적인 소통 창구다. 생성자 주입(Constructor Injection)은 Dependent의 생성자가 Dependency를 인자로서 받게끔 만드는 방식을 말한다. 안드로이드 프로젝트에서 가장 권장되는 Dependency Injection 유형이기도 하다.
 
전체 소스 코드

더보기
/* ~ 실행 방법 ~
 * 1. https://play.kotlinlang.org에 접속
 * 2. 여기에 있는 코드 복사
 * 3. 접속한 페이지에 붙여 넣기
 * 4. 접속한 페이지에 있는 Run 버튼 클릭
 *
 * ~ 실행 결과 미리 보기 ~
 * Crankshaft is ready
 * Cylinder is ready
 * Piston is ready
 * Engine is ready
 * Airbag is ready
 * Battery is ready
 * Car is ready
 */

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston(crankshaft, cylinder)
    val engine = Engine(piston)
    val airbag = Airbag()
    val battery = Battery()

    val car = Car(engine, airbag, battery)
    car.startCar()
}

class Car(
    private val engine: Engine,
    private val airbag: Airbag,
    private val battery: Battery
) {
    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

class Engine(private val piston: Piston) {
    fun startEngine() {
        piston.startPiston()
        println("${this::class.simpleName} is ready")
    }
}

class Airbag {
    fun startAirbag() {
        println("${this::class.simpleName} is ready")
    }
}

class Battery {
    fun startBattery() {
        println("${this::class.simpleName} is ready")
    }
}

class Piston(
    private val crankshaft: Crankshaft,
    private val cylinder: Cylinder
) {
    fun startPiston() {
        crankshaft.startCrankshaft()
        cylinder.startCylinder()
        println("${this::class.simpleName} is ready")
    }
}

class Crankshaft {
    fun startCrankshaft() {
        println("${this::class.simpleName} is ready")
    }
}

class Cylinder {
    fun startCylinder() {
        println("${this::class.simpleName} is ready")
    }
}

 

#2-2 Method Injection (함수 주입)

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston()
    piston.setDependencies(crankshaft, cylinder)
    val engine = Engine()
    engine.setPiston(piston)
    val airbag = Airbag()
    val battery = Battery()

    val car = Car()
    car.setDependencies(engine, airbag, battery)
    car.startCar()
}

class Car {
    private lateinit var engine: Engine
    private lateinit var airbag: Airbag
    private lateinit var battery: Battery

    fun setDependencies(
        engine: Engine,
        airbag: Airbag,
        battery: Battery
    ) {
        this.engine = engine
        this.airbag = airbag
        this.battery = battery
    }

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

클래스에 dependency들을 초기화하는 함수(Method)를 두는 방식이다.

전체 소스 코드

더보기
/* ~ 실행 방법 ~
 * 1. https://play.kotlinlang.org에 접속
 * 2. 여기에 있는 코드 복사
 * 3. 접속한 페이지에 붙여 넣기
 * 4. 접속한 페이지에 있는 Run 버튼 클릭
 *
 * ~ 실행 결과 미리 보기 ~
 * Crankshaft is ready
 * Cylinder is ready
 * Piston is ready
 * Engine is ready
 * Airbag is ready
 * Battery is ready
 * Car is ready
 */

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston()
    piston.setDependencies(crankshaft, cylinder)
    val engine = Engine()
    engine.setPiston(piston)
    val airbag = Airbag()
    val battery = Battery()

    val car = Car()
    car.setDependencies(engine, airbag, battery)
    car.startCar()
}

class Car {
    private lateinit var engine: Engine
    private lateinit var airbag: Airbag
    private lateinit var battery: Battery

    fun setDependencies(
        engine: Engine,
        airbag: Airbag,
        battery: Battery
    ) {
        this.engine = engine
        this.airbag = airbag
        this.battery = battery
    }

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

class Engine {
    private lateinit var piston: Piston

    fun setPiston(piston: Piston) {
        this.piston = piston
    }

    fun startEngine() {
        piston.startPiston()
        println("${this::class.simpleName} is ready")
    }
}

class Airbag {
    fun startAirbag() {
        println("${this::class.simpleName} is ready")
    }
}

class Battery {
    fun startBattery() {
        println("${this::class.simpleName} is ready")
    }
}

class Piston {
    private lateinit var crankshaft: Crankshaft
    private lateinit var cylinder: Cylinder

    fun setDependencies(
        crankshaft: Crankshaft,
        cylinder: Cylinder
    ) {
        this.crankshaft = crankshaft
        this.cylinder = cylinder
    }

    fun startPiston() {
        crankshaft.startCrankshaft()
        cylinder.startCylinder()
        println("${this::class.simpleName} is ready")
    }
}

class Crankshaft {
    fun startCrankshaft() {
        println("${this::class.simpleName} is ready")
    }
}

class Cylinder {
    fun startCylinder() {
        println("${this::class.simpleName} is ready")
    }
}

 

#2-3 Field Injection (멤버 변수 주입)

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston()
    piston.crankshaft = crankshaft
    piston.cylinder = cylinder
    val engine = Engine()
    engine.piston = piston
    val airbag = Airbag()
    val battery = Battery()

    val car = Car()
    car.engine = engine
    car.airbag = airbag
    car.battery = battery
    car.startCar()
}

class Car {
    public lateinit var engine: Engine
    public lateinit var airbag: Airbag
    public lateinit var battery: Battery

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

클래스의 프로퍼티를 public으로 설정하는 방식이다.

전체 소스 코드

더보기
/* ~ 실행 방법 ~
 * 1. https://play.kotlinlang.org에 접속
 * 2. 여기에 있는 코드 복사
 * 3. 접속한 페이지에 붙여 넣기
 * 4. 접속한 페이지에 있는 Run 버튼 클릭
 *
 * ~ 실행 결과 미리 보기 ~
 * Crankshaft is ready
 * Cylinder is ready
 * Piston is ready
 * Engine is ready
 * Airbag is ready
 * Battery is ready
 * Car is ready
 */

fun main() {
    val crankshaft = Crankshaft()
    val cylinder = Cylinder()
    val piston = Piston()
    piston.crankshaft = crankshaft
    piston.cylinder = cylinder
    val engine = Engine()
    engine.piston = piston
    val airbag = Airbag()
    val battery = Battery()

    val car = Car()
    car.engine = engine
    car.airbag = airbag
    car.battery = battery
    car.startCar()
}

class Car {
    public lateinit var engine: Engine
    public lateinit var airbag: Airbag
    public lateinit var battery: Battery

    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        println("${this::class.simpleName} is ready")
    }
}

class Engine {
    public lateinit var piston: Piston

    fun startEngine() {
        piston.startPiston()
        println("${this::class.simpleName} is ready")
    }
}

class Airbag {
    fun startAirbag() {
        println("${this::class.simpleName} is ready")
    }
}

class Battery {
    fun startBattery() {
        println("${this::class.simpleName} is ready")
    }
}

class Piston {
    public lateinit var crankshaft: Crankshaft
    public lateinit var cylinder: Cylinder

    fun startPiston() {
        crankshaft.startCrankshaft()
        cylinder.startCylinder()
        println("${this::class.simpleName} is ready")
    }
}

class Crankshaft {
    fun startCrankshaft() {
        println("${this::class.simpleName} is ready")
    }
}

class Cylinder {
    fun startCylinder() {
        println("${this::class.simpleName} is ready")
    }
}

 

#3 전체 소스 코드 모음

dependecyInjection.zip
0.00MB

의존성 주입을 구현하기 전 + 의존성 주입의 3가지 유형

 

#4 요약

의존성은 주입은 '노출'의 문제다.