DI/IoC
DI (Dependency Injection)
DI는 의존성 주입이란 뜻으로 의존성을 인수로 취해 주입 받는 것이다.
직접 의존성을 생성하지 않고 주입 받는 이유는 좋은 객체를 설계하기 위해 객체지향 원칙인 OCP, DIP를 지키기 위해서이다.
OCP (Open-Closed Principle)
확장에는 열려있으나 변경에는 닫혀 있어야한다.
다시말해 확장이 쉬워야하며(확장 열림), 어떤 변경에 구현 객체를 직접 코드 수정이 일어나지 않아야 한다.(변경이 닫힘)
주문 서비스를 예를들어보자. 만약 할인 정책이 변경되면 주문 서비스 코드를 변경하지 않고 할인 정책을 변경할 수 있어야 한다.
DIP (Dependency Inversion Principle)
저수준이 고수준을 의존하게 하는 것이다. 다시말해 고수준은 추상화에 의존해야하며, 구체화에 의존하면 안된다.
아래 주문 서비스는 고수준인 OrderService가 저수준인 FixDiscountPolicy에 직접 의존하고 있다.
만약 할인 정책을 변경하려면 고수준인 OrderService 코드를 변경해야한다. 즉, 이는 DIP를 위반한 것이다. (OCP도 위반)
1
2
3
4
5
6
7
8
class OrderService{
private val discountPolicy: DiscountPolicy = FixDiscountPolicy()
fun order(): Order {
val discountPrice: Int = discountPolicy.discount(price)
return Order()
}
}
이를 해결하기 위해 추상화 기술 중 하나인 인터페이스를 하나 만들고 이를 사용하면 DIP, OCP를 지킬 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface DiscountPolicy{
fun discount(price: Int): Int
}
class FixDiscountPolicy: DiscountPolicy{
override fun discount(price: Int): Int {
}
}
class RateDiscountPolicy: DiscountPolicy{
override fun discount(price: Int): Int {
}
}
class OrderService{
private val discountPolicy: DiscountPolicy
fun order(memberId:Int, itemId:Int, price:Int): Order {
val discountPrice: Int = discountPolicy.discount(price)
return Order(memberId, itemId, price, discountPrice)
}
}
DI로 OCP, DIP 지키기
하지만 방금 위의 DIP 코드에서 order 메서드를 사용하면 discountPolicy에 대한 실제 구체 클래스에 대한 정보가 없으므로 당연히 null pointer exception이 발생할 것이다.
이 때 필요한 것이 DI 의존성 주입이다. 이제 DI를 사용하면 완벽히 클라이언트 코드(고수준 order service)를 변경하지 않고, 사용할 구현 할인 정책 객체(저수준)를 쉽게 변경해 연결 할 수 있다.
아래는 DI를 사용하고 OrderService 생성자에 FixDiscountPolicy를 의존 성 주입한 예시이다.
1
2
3
4
5
6
7
8
class OrderService(private val discountPolicy: DiscountPolicy){
fun order(memberId:Int, itemId:Int, price:Int): Order {
val discountPrice: Int = discountPolicy.discount(price)
return Order(memberId, itemId, price, discountPrice)
}
}
val orderService = OrderService(FixDiscountPolicy())
이렇게 DI를 사용하면 클라이언트 코드를 변경하지 않고도 사용할 구현 객체를 변경할 수 있다. 객체지향 원칙인 OCP, DIP를 지킬 수 있게 되는 것이다.
IoC (Inversion of Control)
IoC는 제어의 역전이란 뜻으로 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다.
제어권은 보통 개발자가 가지고 있지만, IoC가 일어나면 어떤 주체가 어떤 객체에 대한 제어권을 가지고 있게 된다.
예를들어 프레임워크는 자신들의 설계 대로 프로그램이 동작하도록 규격을 만들었고, 사용자에겐 단지 특정 규격을 지킨 부품만을 요구한다. 어떤 객체의 생명주기나 사용 흐름은 프레임워크가 관리하고 사용자는 단지 프레임워크가 제공하는 규격에 맞추는 제어의 역전이 일어난 것이다.
또 다른 예로는 템플릿 메서드 패턴을 들 수 있다. 템플릿 메서드 패턴은 변경이 없는 곳은 상위 클래스로 변경이 잦은 곳은 하위 클래스로 둔다. 하위 클래스에서 오버라이딩한 메서드는 하위 클래스가 자체가 관리하는 게 아닌 외부의 상위 클래스가 호출하고 흐름을 관리한다. 따라서 이 경우도 제어의 역전이 일어났다고 할 수 있다
따라서 DI도 IoC를 이루기 위한 한 방법이다. DI는 의존성을 주입 받는 것이고, 이는 객체의 제어권을 직접 관리하는 것이 아닌 외부에서 의존성을 주입 받는 제어의 역전인 것이다.