Dependency Injection 의존성 주입

ellyheetov·2021년 5월 1일
3
post-thumbnail
post-custom-banner

클래스 사이에 의존성을 줄이기 위해서 델리게이트를 사용....프로토콜을 사용...
의존성을 줄이면 유지보수를 향상 시킬수 있으며...

의존성이 뭐길래 이렇게 신경을 써야하는 걸까? 🤷🏻‍♀️

의존성(Dependency)이란?

간단히 말해서, 어떤 클래스가 다른 클래스의 인터페이스를 이용하는 경우를 말한다.

예시

나만의 특별한 차를 가지고 있다고 가정해보자. 이 차는 내가 원하는 어떤 엔진이든 갈아 끼울 수 있다.

protocol Propulsion {
    func move()
}

class Vehicle
{
    var engine: Propulsion

    init() {
        engine = RaceCarEngine()
    }

    func forward() {
        engine.move()
    }
}

move() 함수를 가지고 있는 프로토콜 Propulsion이 있다.

Vehicle 클래스는 engine이라는 하나의 프로퍼티를 가지고 있는데 프로퍼티의 타입은 Propulsion이다. engine이 어떠한 타입이든 Propulsion 프로토콜만 따르고 있다면 사용이 가능하다. (이것을 타입으로써의 프로토콜이라고 부른다.)

Vehicle 인스턴스가 생성될때, init() 메소드가 호출되고 engine에 RaceCarEngine 인스턴스가 할당된다. forwoard() 메소드가 호출되면 enginemove 메소드가 호출된다.

차의 엔진은 다음과 같은 코드로 이루어져있다.

class RaceCarEngine: Propulsion {
    func move() {
        print("Vrrrooooommm!!")
    }
}

RaceCarEngine 클래스는 Propulsion 프로토콜을 채택하고있고, move()도 구현하고있다.
이제, 다음과 같이 사용할 수 있다.

var car = Vehicle()
car.forward() // Output: Vrrrooooommm!!

Vehicle타입의 차를 선언하였고, forward()메소드를 호출하여 엔진을 가지고 차를 움직인다.

여기에서 의존성이 뭐야?

얼핏 보면, 프로토콜을 사용하고 있으므로 어떤 엔진이 오든 move() 함수를 이용하여 실행할 수 있을 것 처럼 보인다.

var engine: Propulsion

func forward() {
    engine.move()
}

그런데, 의존성은 RaceCarEngine 클래스에 있다.

Vehicle 내부에init()을 보자.

init() {
    engine = RaceCarEngine()
}

Vehicle은 RaceCarEngine과 강한 결합으로 맺어져 있다. 다시말해, Vehicle 클래스는 직접적으로 RaceCarEngine를 참조 하고 있다.

Vehicle로 부터 RaceCarEngine을 직접적으로 참조하고 있기 때문에 Vehicle은 RaceCarEngine에 의존하게 된다. 이것이 의존성이다.

어떻게 하면 의존성을 없앨 수 있을까? 여기서부터 의존성 주입이 나오게 된것이다.

의존성 주입

이 방법이 의존성을 전적으로 피할 수 있는 것은 아니다. 단지 좋은 하나의 방법일 뿐이다.

오해하지 말아야 할 것은, 의존성이 나쁜것은 아니다. 앱의 여러가지 코드가 한데 어우러져 상호작용할 수 있는 것은 의존성 덕분이다.

하지만 강한 결합, 강한 의존 관계는 피하자는 것이다.

스파게티 코드를 짜지 않기 위해서는 모듈화 하고, 강한 결함을 끊고, 의존성을 관리해야한다.

어떻게 의존성을 줄일 수 있을까?

Vechicle클래스로 돌아가서 어떻게 의존성을 줄일 수 있을까?

class Vehicle
{
    var engine: Propulsion

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

    func forward() {
        engine.move()
    }
}

아주 작은 변화이지만 많은 변화를 발생시킨다.

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

먼저, 인스턴스의 생성시점을 분리한다.

Vehicle의 초기화 과정에서 RaceCarEngine을 생성하지 않는다. Vehicle 클래스는 더이상 RaceCarEngine을 알 필요가 없어졌다. 외부에서 생성하여 값을 넣어주는데 init 메소드에 파라미터를 이용한다. 이때, 파라미터로 넘어온 engine은 Propulsion 타입이다.

이제 다음과 같이 사용할 수 있다.

let fastEngine = RaceCarEngine()

var car = Vehicle(engine: fastEngine)
car.forward() // Output: Vrrrooooommm!!

RaceCarEngine의 인스턴스를 먼저 생성한후, Vehicle을 생성한다.
외부에서 engine을 생성하고 Viechine에 프로퍼티에 대입하게 된다. 이것이 의존성 주입이다.
두 클래스는 여전히 서로 의존하고 있지만, 더 이상 강한 결합은 아니다.

만약 다른 엔진이 있다고 해보자.

class RocketEngine: Propulsion {
    func move() {
        print("3-2-1... IGNITION!... PPPPSSSSCHHHHOOOOOOOOOMMMMMM!!!")
    }
}

RaceCarEngine을 RocketEngine 엔진으로 바꾸어보자. 단지 Propulsion 프로토콜만 따르고 있다면 다음과 같이 사용할 수 있다.

Vehicle의 내부 코드 변경없이 다음과 같이 사용할 수 있게 된 것이다.

let rocket = RocketEngine()

var car = Vehicle(engine: rocket)
car.forward() // 3-2-1... IGNITION!... PPPPSSSSCHHHHOOOOOOOOOMMMMMM!!!

RocketEngine을 생성하고, Vehicle 생성시에 rocket 객체를 넣어주면된다.

만약 의존성 주입없이 Vehicle 내부에서 인스턴스를 생성 해야한다면 새로운 엔진이 만들어 질때마다 Vehicle에 Switch case를 추가해야하는 일이 발생할 수도 있다.

프로토콜 덕분이지!

의존성 주입에서 가장 중요한 부분은 프로토콜 타입이다. 위에 두 엔진은 모두 프로토콜을 따르고 있다. 이것이 서로 다른 엔진을 바꿔치기 할 수 있는 이유이다.

그렇다고 항상 프로토콜이 필요할까??

그건아니다. subclassing, generics, opaque types등을 이용할 수 있다.

참고

https://learnappmaking.com/dependency-injection-swift/

profile
 iOS Developer 좋아하는 것만 해도 부족한 시간
post-custom-banner

2개의 댓글

comment-user-thumbnail
2021년 7월 21일

정리 잘하시네요. 굿굿

1개의 답글