의존성
- 어떤 대상이 참조하는 객체.
- 어떤 대상 → A
- 어떤 대상이 A를 참조한다. → 어떤 대상이 A를 가지고 있다.
- 자동차가 엔진을 참조하고 있다. → 자동차가 엔진을 가지고 있다.
class Car {
val engine = Engine()
}
- Car는 Engine에 의존한다. or Car는 Engine에 의존적이다.
- Engine은 Car의 의존성임.
의존성 주입
class Car(val engine: Engine) {
}
- engine을 생성자 매개변수로 옮김. → engine생성의 책임을 제거함.
- 위 설계 패턴을 IoC(제어의 역전)라고 부르고, 객체 생성의 책임을 내부에서 외부로 뒤집으며 engine에 대한 제어를 역전시킴.
- engine 의존성을 외부에서 주입 받을 수 있게 해줌.
class Car(val engine: Engine) {
}
- 자동차는 엔진외 많은 부품으로 이뤄져 있음.
- Car 클래스에서 이러한 부품들을 전부 가지고 있는다면, 아주 거대한 소스코드를 갖게 될 것임.
- IoC를 통해서 필요한 의존성들을 외부에서 주입 받으므로 긴 소스코드와 책임이 줄어들게 됨.
class Car(
val engine: Engine,
val wheels: Wheels,
val wiper: Wiper,
val battery: Battery
) {
}
- 하나의 기능만 책임질 수 있도록 캡슐화하는 원칙을 단일 책임 원칙이라 부름.
- Engine, Wheels, Wiper, Battery가 단일 책임 원칙을 적용한 클래스임.
- IoC를 적용하여 주입을 하게 되면, Car 클래스가 가져야 할 비즈니스 로직에 더욱 집중이 가능함.
Injector
- 의존성을 클라이언트(객체)에게 제공하는 역할임.
- 어떤 의존성을 객체에 제공할 땐 보통 Inject개념을 통해서 이뤄짐.
fun main(args: Array<String>) {
val engine = Engine()
val car = Car(engine)
}
class Injector {
fun getEngine(): Engine = Engine()
}
fun main(args: Array<String>) {
val engine = Injector().getEngine()
val car = Car(engine)
}
- 메인 함수에서 엔진을 생성하는 코드를 작성하지 않고, 인젝터에게 위임함.
- 여러가지의 엔진을 공유하고 싶다면, 인젝터 클래스에 엔진을 생성하는 코드를 작성하여 자원공유를 할 수 있음.
- Injector의 또다른 명칭은 다음과 같음.
- Container
- Assembler
- Component
- Provider
- Factory
의존성 주입의 장점
- Car 클래스의 코드를 변경하지 않고, 단순히 어떤 엔진(객체)를 상속하여 확장한 여러 타입의 엔진을 할당하는 것이 가능함.
- GasolineEngine(), DieselEngine()은 Engine을 상속받고 있음.
val gasolineCar = Car(GasolineEngine())
val dieselCar = Car(DieselEngine())
- Car 클래스의 소스코드를 변경하지 않음. → 재사용성
- 클래스간 결합도를 느슨하게 만들어 줌. → 디커플링
class Car {
val gasolineEngine = GasolineEngine()
}
의존성 주입을 사용하지 않는다면, 기본 엔진에서 가솔린 엔진으로 교체하는 경우 Car 클래스의 수정이 일어나게됨.
class CarTest {
@Test
fun `Car 성공 케이스 테스트`() {
val car = Car(FakeEngine())
}
@Test
fun `Car 실패 케이스 테스트`() {
val car = Car(FakeBrokenEngine())
}
}
open class Engine() {
}
class Car(val engine: Engine) {
}
class FakeEngine(): Engine() {
}
class FakeBrokenEngine(): Engine() {
}
- Mock 객체를 주입(사용)하여 실 데이터의 동작들을 완전히 제어가 가능함.
- 이는 특정 동작들을 설정하거나 결과 확인이 가능함.
- 실제 사용되는 객체보다 더욱 무거운 객체를 만들어 테스트할 수 있음. → FakeBrokenEngine
- DI를 사용 안하고, Car 클래스 내에서 직접 엔진을 생성하는 경우 실제 엔진을 사용하게 됨.
- 동작을 테스트 시나리오에 맞게 제어하기 어려움.
- 코드를 반복적으로 쓰고 지우기 번거로움.
- 개발자의 실수로 중요 로직을 삭제하거나 변경될 수 있음.
- 새로운 엔진을 테스트 하려면 Car 클래스의 코드를 변경해야함.
- 보일러 플레이트 코드가 감소됨.
- IoC를 통해서 필요한 의존성들을 외부에서 주입 받으므로 긴 소스코드와 책임이 줄어들게 됨.
- 의존성 관리가 용이함(자원공유)