Post

의존 관계 주입 (DI)


처음 스프링을 공부할 때 의존 관계 주입이 정말 헷갈렸다. 그 이유는 의존성이라는 개념이 잡혀있지 않았고 의존 관계 주입이라는 용어를 암기식으로 공부했었다. 하지만 의존 관계 주입은 객체 지향 설계 원칙에 정말 중요했다. 따라서 이번 글에서 김영한 강사님의 강의를 들으며 깨달은 의존 관계 주입에 대해서 복습하는 글을 남겨보려고 한다.


1. 의존성(의존 관계)이란?



의존성 주입을 이해하기 위해서는 의존성이라는 개념을 명확하게 이해하고 넘어가는 것이 제일 중요한 것 같다. 그럼 의존성이란 무엇일까?
게임을 예로 들어보자. 한 캐릭터가 있고 해당 캐릭터는 검사와 궁수라는 두개의 직업을 번갈아 가며 사용할 수 있고 각 직업은 공통적으로 공격, 방어, 스킬1,스킬2를 갖는다.

이 예시에서 우리는 두 직업의 공통점을 뽑아 인터페이스로 만들어 볼 수 있다.

1
2
3
4
5
6
7
publuc interface Job{

  void attack();
  void defense();
  void skill1();
  void skill2();
}

이후 검사와 궁수 클래스를 job을 구현하여 만들어 보면 아래와 같다. 각 메서드의 자세한 구현은 skip 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
publuc class Swordman implements Job{

  @Override
  void attack(){
    ...
  }
  @Override
  void defense(){
    ...
  }
  
  @Override
  void skill1(){
    ...
  }
  
  @Override
  void skill2(){
    ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
publuc class Bowman implements Job{

  @Override
  void attack(){
    ...
  }
  @Override
  void defense(){
    ...
  }
  
  @Override
  void skill1(){
    ...
  }
  
  @Override
  void skill2(){
    ...
  }
}

다음으로, 캐릭터를 만들고 직업을 검사로 부여해보자

1
2
3
4
5
6
publuc class Character{

  private Job job = new Swordman();

  ... 
}

이 예시에서 Character 클래스의 의존 관계를 분석해 보자. 먼저 job이라는 인터페이스와 의존 관계가 있고 Swordman이라는 구현 클래스에도 의존 관계가 있다.

DI

여기서 의존 관계란 무엇일까? Character 클래스가 어떤 클래스와 인터페이스를 알고있는지다. 현재 Character 클래스는 job 인터페이스를 알고있고 Swordman 클래스도 알고있다. 의존 관계란 바로 이런 특정 클래스에서 알고있는 상태를 뜻한다.


2. 의존성 주입이란?



그럼 두개의 클래스에 의존성이 있다는 걸 알았는데 의존성 주입은 무엇일까? 의존성 주입을 알기 위해서는 객체 지향 설계 원칙에 대해 알아야한다.

객체 지향 설게 원칙 SOLID에는 총 5가지 원칙이 존재하는데 그중 OCP와 DIP에 대해서만 다루어보자.

OCP는 확장에는 열려있고 변경에는 닫혀있어야한다는 원칙이고 DIP는 추상 클래스에만 의존해야하고 구체 클래스에는 의존하면 안된다는 원칙이다.

작성한 Character는 먼저 직업을 바꾸기 위해서는 Swordman을 Bowman으로 변경해야한다 따라서 OCP를 지키지 않는다는 사실을 알 수 있다. 또한 위에서 살펴본 의존 관계를 보면 추상 클래스 뿐만 아니라 구체 클래스에도 의존하고 있으므로 DIP도 지키고 있지 않다고 볼 수 있다.

이런 배경에서 등장한 것이 바로 의존성 주입(DI)이다. 클라이언트 코드(Character 클래스)에서 구현 클래스를 선언하는 것이 아니라 설정 클래스를 따로 두고 구현과 관련된 의존성을 주입해 주는 방법을 말한다. 코드 예시를 살펴보자.

1
2
3
4
5
6
7
8
9
10
publuc class Character{

  private Job job;

  public Character(Job job){
    this.job = job;
  }

  ... 
}
1
2
3
4
5
6
7
publuc class GameConfig{

  public Job putJob(){
    return new Character(new Swordman());
  }
  ... 
}

GameConfig라는 설정 파일을 외부에 두면서 생성자를 통해 구체 클래스 의존 관계를 주입해주는 것을 볼 수 있다. 이렇게 Character의 구체 클래스 의존 관계를 지워서 DIP 원칙을 지키고 클라이언트 코드의 변경없이 Swordman에서 Bowman으로 변경할 수 있으므로 OCP도 지킬 수 있게 되었다.

결론적으로, 의존성 주입은 객체 지향 설계 원칙을 지키기 위한 노력의 결과물이었던 것이다.


3. 마무리하며…



저는 처음에 의존성이라는 개념과 의존성 주입이라는 개념을 딱딱하게 뜻 풀이로 접근하였습니다. 또한 객체 지향 설계 원칙에 대한 제대로 된 이해 없이 스프링을 사용하며 의존성 주입에 대해 공부하였고 그 결과는 객체 지향 설계 원칙을 지키지 못한 코드들의 탄생이었습니다.

1년이란 시간이 지난 후에 이렇게 부족했던 개념을 채우며 객체 지향 설계 원칙을 지키는 코드를 작성하는데 한발짝 나아갔다고 생각합니다.

This post is licensed under CC BY 4.0 by the author.