Java - 의존성 주입(Dependency Injection)이란?

의존성 주입(Dependency Injection)은 객체의 생성과 사용 관심사를 분리하는 프로그래밍 설계 방식입니다. 자바 같은 경우 추상화 도구(인터페이스, 추상 클래스)를 사용하면 손쉽게 의존성 주입하는 코드를 만들 수 있습니다.

 

 오늘은 의존성 주입(DI)이라는 단어가 주는 부담을 덜어낼 수 있도록 이 글을 작성하게 되었습니다. 아직까지 의존성 주입이라는 개념이 대하기 어려우셨다면 뒤에서 이야기할 이야기를 통해 조금이라도 의존성 주입을 쉽게 이해하실 수 있으면 좋겠습니다. 아 그리고 의존성 역전(IOC)은 의존성 주입과 다른 개념이니 의존성 주입과 의존성 역전을 혼동하지 말아 주세요. 분명 둘은 다른 개념입니다.

의존성 정의부터 알아보자

 의존성이라는 단어의 사전적인 정의는 "다른 것에 의지하여 생활하거나 존재하는 성질"입니다. 코드에서 의존성이란 어떤 걸까요? 앞서 이야기한 사전적인 정의와 같지만 코드로 의존성을 풀이하면 "다른 객체를 얼마만큼 구체적으로 아느냐"라고 말할 수 있습니다. 코드로 예를 들어볼까요?

public class Main {
  public static void main(String[] args) {
    A a = new A();

    System.out.println(a.step1.step2.step3);
  }
}

class A {
  B step1 = new B();
}

class B {
  C step2 = new C();
}

class C {
  int step3 = 10;
}

 예제 코드의 주제는 "step3 변수의 10을 사용하자"입니다. main() 함수가 step3의 10이라는 값에 접근하기 위해서는  A, B, C까지 알아야 합니다. 다시 말해 main() 함수는 A 객체에는 무엇이 있고 B 객체는 어떤 것이 있고 또 C 객체의 어떤 변수인지 main()은 직접적이면서도 매우 구체적으로 알아야 합니다. 프로그래밍에서는 이를 의존성이라고 합니다.

의존성 주입 (Dependency Injection)

 앞서 의존성을 "다른 객체를 얼마만큼 구체적으로 아느냐"라고 이야기했는데 그럼 의존성 주입은 어떻게 말할 수 있을까요? 딱딱하게 이야기하면 객체의 생성과 사용 관심사를 분리하는 방법이라고 말하겠지만 코드로 풀이해보면 "객체는 내가 만들게 넌 사용해"입니다. 

 

 자 이번에도 말 대신 예제 코드를 통해 의존성을 주입하는 방법을 알아보겠습니다.

public class Main {

  public static void main(String[] args) {
    Controller controller = new Controller();
    controller.print();
  }
}

class Controller {

  private Service service;

  public Controller() {
    this.service = new Service();
  }

  public void print() {
    System.out.println(service.message());
  }
}

class Service {

  public String message() {
    return "Hello World!";
  }
}

 예제 코드로 작성한 내용은 Controller 객체가 Service 객체를 접근해서 메시지인 "Hello World!"를 출력하는 코드입니다. 의존성을 설명할 때와 마찬가지로  두 객체 사이에는 직접적인 관계가 있습니다. Controller 객체는 Service 객체를 알고 있고 직접 만들어서 메시지를 사용합니다.

 

 이번에는 직접적인 관계에 있는 Controller 객체와 Service 객체 사이에 의존성을 주입해 간접적인 관계가 될 수 있도록 만들어보겠습니다.

public class Main {

  public static void main(String[] args) {
    Controller controller = new Controller(new MessageService());
    controller.print();
  }
}

interface IService {

  String message();
}

class Controller {

  private IService service;

  public Controller(IService service) {
    this.service = service;
  }

  public void print() {
    System.out.println(service.message());
  }
}

class MessageService implements IService {

  public String message() {
    return "Hello World!";
  }
}

 예제 코드를 실행시키면 화면에는 "Hello World!"가 출력됩니다. 예제 코드의 결과는 동일하지만 의존성을 주입할 수 있도록 코드를 수정하였습니다. 이제는 Controller 객체가 MessageService 객체를 생성하지 않고 인터페이스인 IService 객체를 사용해 메시지를 출력합니다. 이전 코드와 달리 Controller 객체는 MessageService 객체를 몰라도 IService를 이용해 메시지를 출력할 수 있습니다.

 

 더 이상 Controller 객체와 Message 객체의 사이는 이전처럼 구체적이지 않습니다. 여전히 구체적으로 알긴 하지만 이전보다는 구체적이지 않습니다. 이제 Controller 객체는 new 키워드를 사용해 Message 객체를 생성하지 않습니다. 코드 라인 수가 증가하고 객체가 더 늘어났지만 이젠 생성하고 사용하는 직접적인 관계가 아닌 사용만 하는 조금 직접적인 관계가 되었습니다. 정말 "객체(MessageService)는 내가(Main) 만들게 넌 사용(IService) 해"라는 상황이 되었습니다.

 그렇다면 어떻게 관심사가 분리된 걸까요? 바로 간호사가 주사로 약을 놔주듯이 main() 함수에서 MessageService를 주입했기 때문입니다. Controller 객체와 Service 객체가 직접적인 관계에 있을 때는 지금처럼 main()이 간호사처럼 객체를 생성해서 주입해주지 않았어요.

의존성을 주입하는 이유

 지금까지 의존성 주입이 어떤 것인지 알아보았습니다. 그렇다면 우리는 왜 의존성을 주입하려고 하는 걸까요? 굳이 코드 라인의 수를 늘리고 복잡하게 객체를 추가하면서까지 말이죠.

 

 그 이유는 바로 생성과 사용에 대한 관심을 분리하게 되면 생성에 대한 책임을 다른 누군가에 위임할 수 있는 동시에 필요에 따라 객체 생성 방식을 선택할 수 있기 때문입니다. 최종적으로는 객체들이 가지는 강한 결합을 느슨하게 만들 수 있고 이는 설계의 유연성을 부여합니다.

DependencyInjectionSample.zip
0.03MB

 이 글에서 사용한 예제 코드는 첨부파일로 제공되니 필요하신 분은 다운로드해서 사용하시면 됩니다.

반응형

댓글

Designed by JB FACTORY