의존 관계와 DI

김민우·2022년 8월 6일
0

잡동사니

목록 보기
3/22

의존관계란?

다음과 같은 상황을 가정해보자.

피자 셰프가 레시피를 봐야만 피자를 만들 수 있다. 요리사는 레시피가 변경되면 새로운 피자를 만들게 된다. 따라서, 요리사는 레시피에 의존하는 관계라 할 수 있다. 둘은 함께 바뀔 가능성이 있다 정도로 이해하자.

이를 간단히 코드로 살펴보자.

public class PizzaChef {
	
    private PizzaRecipe pizzarecipe;
    
    public PizzeRecipe() {
    	this.pizzarecipe = pizzarecipe;
   	}
    
    ...
}   
  • PizzaChef 객체는 PizzaRecipe 객체에 의존하는 것을 알 수 있다.

이러한 구조는 다음과 같은 문제가 존재한다.

  1. 두 클래스의 결합성이 높다
    PizzaChef 클래스는 PizzaRecipe 클래스와 강하게 결합되어 있다는 문제점을 가지고 있다. 만약 PizzaChef가 새로운 레시피인 CheezePizzaRecipe 클래스를 이용해야 한다면 PizzaChef 클래스의 생성자를 변경해야만 한다. 만약 이후 레시피가 계속해서 바뀐다면 매번 생성자를 바꿔줘야 하는 등, 유연성이 떨어지게 된다. 

  2. 객체들 간의 관계가 아닌 클래스 간의 관계가 맺어진다
    객체 지향 5원칙(SOLID) 중 "추상화(인터페이스)에 의존해야지, 구체화(구현 클래스)에 의존하면 안 된다"라는 DIP 원칙이 존재한다. 현재 PizzaChef 클래스는 PizzaRecipe 클래스와 의존 관계가 있다. 즉, PizzaChef는 클래스에 의존하고 있다. 이는 객체 지향 5원칙을 위반하는 것으로 PizzaChef 클래스의 변경이 어려워지게 된다. 

이러한 문제점을 해결할 수 있는 것이 바로 의존관계 주입(DI)이다.


의존관계 주입 (DI)

DI란 의존관계를 외부에서 주입해주는 것을 말한다. 스프링에서는 이러한 DI를 담당하는 DI 컨테이너가 존재한다. 이 DI 컨테이너가 객체들 간의 의존 관계를 주입한다. 

위의 문제점들을 DI를 활용하여 해결해보자. 일단, 다양한 피자 레시피를 추상화해야 하므로 PizzaRecipe를 인터페이스로 만들고, CheezePizzeRecipe 같은 레시피는 구현체로 만들자.

public interface PizzaRecipe {
	...
}

public class CheezePizzaRecipe implements PizzaRecipe {
	...
}

그리고, 이에 대한 구현체는 외부에서 주입받도록 하자.

public class pizzaChef {
	private PizzaRecipe pizzaRecipe;
    
    public PizzaChef(PizzaRecipe pizzaRecipe) {
    	this.pizzaRecipe = pizzaRecipe;
    }
}	

이것이 OCP와 DIP를 지켰는지 확인해보자.

  1. OCP를 지켰는가?
    지켰다. 다른 레시피를 바꾸기 위해선 PizzaChef 클래스를 수정할 필요가 없기 때문이다. PizzaChef는 어떤 레시피가 오는지 모르고 묵묵히 피자만 만들 뿐이다.
  1. DIP를 지켰는가?
    지켰다. PizzaChef는 추상화가 아닌 PizzaRecipe 인터페이스에 의존하기 때문이다.

위 설계가 어떻게 작동하는지 대충 감이 온다. 일단, 설정 정보와 구현을 완전히 분리했다. PizzaChef는 어떤 레시피가 오는지 모르고 묵묵히 피자만 만들 뿐이고, 어떤 레시피를 줄지는 다른 것이 결정하기 때문이다.

이렇게, 의존 관계를 내부가 아닌 외부에서 주입하는 것을 의존관계 주입(DI)라 한다.

DI를 사용하면 다음과 같은 장점들이 존재한다.

  1. 결합도가 줄어든다
    어떤 객체가 다른 객체에 의존한다는 것은, 그 의존 대상의 변화에 취약하다는 뜻이다. DI를 이용하면 주입받는 대상이 바뀔지 몰라도 해당 객체의 구현 자체를 수정할 일은 없어진다.

  2. 유연성이 높아진다
    기존 PizzaChef 클래스는 피자 레시피를 바꾸는 것이 쉽지 않았다. 생성자 코드 자체를 변경해주어야 했지만, DI를 이용하면 생성자의 인수만 다른 피자 레시피로 바꿔주면 된다.

  3. 테스트하기 쉬워진다
    DI를 이용한 객체는 자신이 의존하고 있는 인터페이스가 어떤 클래스로 구현되어 있는지 몰라도 된다. 따라서 테스트하기 더 쉬워진다. 

  4. 가독성이 높아진다

0개의 댓글