4T2F / ThinkBig

🌟씽크빅 스터디🌟
5 stars 1 forks source link

DI(Dependency Injection) 의존성 주입 이란 무엇일까요? #65

Open Hsungjin opened 6 months ago

Hsungjin commented 6 months ago

Intro

​ 이번에는 의존성 주입(Denpendency Injection)에 대해서 공부하고 정리해봤습니다. ​ 주제의 선정 이유는 최근 본 면접에서 의존성 주입에 대한 질문을 받았는데 명확하게 답변을 하지 못해 자세히 알아보려고 합니다. ​

What is Dependency ( 의존성 ) ?

​ 객체 지향 프로그래밍에서 Dependency 즉 의존성은 서로 다른 객체 사이에 의존 관계가 있다는 것을 말해요. ​ 즉 의존하는 객체가 수정되면 다른 객체에도 영향을 받는 것을 말해요. ​

struct Drink {
    func coffee() {
        print("아메리카노")
    }
​
    func water() {
        print("물")
    }
}
​
struct Person {
    var todayDrink: Drink

    func coffee() {
        todayDrink.coffee()
    }

    func water() {
        todayDrink.water()
    }
}

​ Person 이라는 객체는 Drink객체를 인스턴스로 사용하고 있으므로, Drink 객체에 의존성이 생겨요 ​ 만약 이때 Drink 객체에 중요한 수정이나 오류가 발생하게 된다면 어떻게 될까요? ​ 당연히 Person 이라는 객체에도 영향을 받을 수 있습니다. ​ 의존성을 가지는 코드가 많아진다면 어떻게 될까요? ​ 재활용성이 떨어지고 매번 의존성을 가지는 객체들을 함께 수정해주어야 한다는 문제가 발생하겠죠? ​ 이러한 의존성을 해결하기 위해 나온 개념이 바로 의존성 주입(Dependency Injection) 입니다. ​

What is Injection ( 주입 ) ?

​ Injection은 내부가 아닌 외부에서 객체를 생성해서 넣어주는 것을 의미해요. ​

class Eat: Menu {
    var coffee: String
    var water: String

    init(coffee: String, water: String) {
        self.coffee = coffee
        self.water = water
    }

    func printCoffee() {
        print("아메리카노")
    }

    func printWater() {
        print("물")
    }
}
​
let menu = Eat(coffee: "아메리카노", water: "물")

​ 다음과 같이 객체를 참조할때 생성자를 활용해서 외부에서 주입할 수 있어요. ​ 그럼 의존성과 주입에 대해 알아봤으니까 의존성 주입에 대해 알아볼까요? ​

Dependency Injection ( 의존성 주입 )

​ 클래스 내부에서 의존하는 객체를 생성하는 것이 아니라, ​ 외부에서 생성한 후 의존하는 객체를 전달받아 사용하는 디자인 패턴이에요. ​ 이를 통해 객체간의 결합도를 낮추고 유연하고 확장성 있는 설계가 가능하도록 도와줍니다. ​ 크게 4가지 방법으로 사용할 수 있습니다. ​ 1. 생성자 주입 ​ 2. 프로퍼티 주입 ​ 3. 메서드 주입 ​ 4. 인터페이스 주입 ​

1. 생성자 주입

​ 생성자를 통해 파라미터 값으로 필요한 의존성을 받아 객체를 생성합니다. ​ 보통 초기에 꼭 필요한 데이터나 객체가 있을 때 사용 됩니다. ​

class Engine {
    func start() {
        print("엔진 가동")
    }
}
​
class Car {
    private let engine: Engine

    init(engine: Engine) {
        self.engine = engine
    }
​
    func start() {
        engine.start()
        print("차가 출발합니다.")
    }
}
​
let engine = Engine()
let myCar = Car(engine: engine)
myCar.start()
​
// 엔진 가동
// 차가 출발합니다.

2. 프로퍼티 주입

​ 객체를 생성한 후 프로퍼티를 통해 의존성을 전달하는 방식입니다. ​

class Engine {
    func start() {
        print("엔진 가동")
    }
}
​
class Car {
    var engine: Engine?

    func start() {
        engine?.start()
        print("차가 출발합니다.")
    }
}
​
let engine = Engine()
let myCar = Car()
​
// Car 인스턴스의 engine 프로퍼티에 Engine 인스턴스 할당
myCar.engine = engine
myCar.start()
​
// 엔진 가동
// 차가 출발합니다.

3. 메서드 주입

​ 의존성이 필요한 메서드를 호출할 때, 해당 메서드에 파라미터 값에 의존성을 전달하는 방식입니다. ​

class Engine {
    func start() {
        print("엔진 가동")
    }
}
​
class Car {
    // Engine 클래스를 파라미터로 전달받음
    func start(with engine: Engine) {
        engine.start()
        print("차가 출발합니다.")
    }
}
​
let engine = Engine()
let myCar = Car()
// Engine 클래스의 인스턴스를 전달함
myCar.start(with: engine)
​
// 엔진 가동
// 차가 출발합니다.

4. 인터페이스 주입

​ 일단 인터페이스란 객체와 객체 사이의 상호작용을 정의하는 추상적인 개념이에요. ​ Swift에서는 보통 Protocol을 이용해 추상화 시켜줍니다. ​ 객체의 내부 구조나 구현 방식에 관계없이 외부에서 객체를 다룰 수 있게 도와줘요 ​

// 프로토콜 정의
protocol EngineProtocol {
    func start()
}
​
// 하위 모듈 - 프로토콜 채택
class Engine: EngineProtocol {
    func start() {
        print("엔진 가동")
    }
}
​
// 상위 모듈 - 생성 시 프로토콜을 파라미터로 받음
class Car {
    private let engine: EngineProtocol

    init(engine: EngineProtocol) {
        self.engine = engine
    }

    func start() {
        // 프로토콜을 이용해 하위 모듈의 메서드 사용
        engine.start()
        print("차가 출발합니다.")
    }
}
​
let engine = Engine()
let myCar = Car(engine: engine)
myCar.start()
​
// 엔진 가동
// 차가 출발합니다.

​ 그럼 여기서 인터페이스 주입 패턴을 이용해서 전기차로 개조를 시켜보면 어떻게 될까요? ​ 상위 모듈의 코드를 수정하지 않고도 확장이 용이해져 깔끔하게 수정할 수 있습니다. ​

// 프로토콜 정의
protocol EngineProtocol {
    func start()
}
​
// 하위 모듈 - 가솔린 엔진
class GasolineEngine: EngineProtocol {
    func start() {
        print("가솔린 엔진 가동")
    }
}
​
// 하위 모듈 - 전기 모터 (추가)
class ElectricMotor: EngineProtocol {
    func start() {
        print("전기모터 가동")
    }
}
​
// 상위 모듈
class Car {
    private let engine: EngineProtocol

    init(engine: EngineProtocol) {
        self.engine = engine
    }

    func start() {
        // 프로토콜을 이용해 하위 모듈의 메서드 사용
        engine.start()
        print("차가 출발합니다.")
    }
}
​
let gasoline = GasolineEngine()
let motor = ElectricMotor()
​
let myGasolineCar = Car(engine: gasoline)
let myElectricCar = Car(engine: motor)
​
myGasolineCar.start()
// 가솔린 엔진 가동
// 차가 출발합니다.
​
myElectricCar.start()
// 전기모터 가동
// 차가 출발합니다.

의존성 주입의 사용 이유는?