[SOLID] DIP: 의존성 역전 원칙

DIP(Dependency Inversion Principle)

  1. 상위 모듈하위 모듈에 의존해서는 안된다.
  2. 추상화는 세부 사항에 의존해서는 안된다.

의존이란?

class Car {
	private val gasolineEngine: GasolineEngine = GasolineEngine()	
    
	fun start() {
		gasolineEngine.start()
	}
}

class GasolineEngine {
	fun start() {
		print("Gasoline Engine Start!")
	}
}

Car 클래스 안에 GasolineEngine 클래스가 있을 때처럼
기능 구현을 위해 다른 구성 요소를 사용하는 것을 의존한다라고 말합니다.

이 코드의 문제점

이후 하이브리드 자동차 추가가 필요해졌는데 이 구조로는 추가가 힘듭니다.

class HybridCar {
	private val hybridEngine: HybridEngine = HybridEngine()
    
	fun start() {
		hybridEngine.start()
	}
}

class HybridEngine {
	fun start() {
		print(" Hybrid Engine Start!")
	}
}

이렇듯 같은 구조임에도 새로운 자동차를 생성해주어야 합니다.
이런 문제를 해결하기 위해 SOLID 패턴의 D인 의존성 역전 원칙이 필요하게 됩니다.

우선 현재까지의 상황을 다이어그램으로 나타내보겠습니다.

코드 수정하기

우선 가솔린 엔진과 하이브리드 엔진의 공통점을 Interface 로 묶어보겠습니다.

interface Engine {
	fun start()
}

class GasolineEngine: Engine {
	override fun start() {
		print("Gasoline Engine Start!")
	}
}

class HybridEngine: Engine {
	override fun start() {
		print("Hybrid Engine Start!")
	}
}

이제 Car 클래스를 수정해보겠습니다.

class Car {
	private val engine: Engine = GasolineEngine()

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

class HybridCar {
	private val engine: Engine = HybridEngine()
	
	fun start() {
		engine.start()
	}
}

중복된 코드를 사용하지 않기 위해 인터페이스를 사용했지만 클래스를 만들어줘야 하는건 동일합니다.

이를 해결하기 위해 DI(Dependency Injection) 의존성 주입이 필요해졌습니다.

의존성 주입

의존성 주입이란 말그대로 "외부에서 의존성주입한다"입니다.

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

내부에서 Engine 을 생성시키는게 아닌 외부에서 주입을 해서 이제 Car는 어떤 엔진이든 Engine 이라는 인터페이스만 지켜진다면 돌려쓸 수 있는 컴포넌트가 되었습니다.

지금까지의 코드를 다이어그램으로 보겠습니다.

  1. Car라는 상위 모듈은 Gasoline, Hybrid 라는 하위 모듈을 의존하지 않게 되었습니다.
  2. Car는 Engine 이라는 인터페이스(추상화)를 의존하게 되었고,
    Gasoline, Hybrid 라는 세부사항은 추상화를 의존하게 되었습니다.

이전 다이어그램과 비교했을 때 세부사항의 의존 방향이 반대가 되었기 때문에
의존성 역전 원칙 이라는 이름으로 불리게 된 것입니다.

코드의 개선점은 더이상 없는지?

이 클래스를 직접 사용할 때 문제점이 하나 더 있습니다.
Car 를 이용하는 Client 가 있다고 해보겠습니다.

fun main() {
	val car = Car(HybridEngine())
}

클라이언트가 직접 하이브리드 엔진을 생성시켜서 자동차에 넣어주어야 합니다.
또 엔진 이외에도 바퀴, 핸들 등이 필요해지면 어떻게 될까요?

fun main() {
	val car = Car(HybridEngine(), Wheel(), Handle())
}

Service 를 사용해야할 클라이언트가 모든 요소들을 제어하고 있습니다.
테스트도 하기 힘들어집니다.

다이어그램으로 제어의 흐름을 살펴보게 되면

보기에도 복잡해보입니다.

이런 문제를 해결하기 위해 제어의 역전이란 개념이 도입되었고 다음 포스팅에서 설명하겠습니다.

다음글 보러가기

포스팅에 문제가 있는 부분은 언제든지 알려주세요!

좋은 웹페이지 즐겨찾기