강좌/Spring 3.0

001_02. 스프링 없이 의존성 주입 1 - 생성자를 통한 의존성 주입

여름나라겨울이야기 2012. 11. 18. 15:38
728x90

000 ~ 005 STS 프로젝트 파일:

ExpertSpring30.zip


의사 코드

운전자가 타이어를 생산한다.
운전자가 자동차를 생산하며 타이어를 장착한다.

Java 로 표현 - 생성자의 인자 이용

Tire tire = new KoreaTire();
Car car = new Car(tire);

주입이란?

주입이란 말은 외부에서라는 뜻을 내포하고 있는 단어입니다.
결국 자동차 내부에서 타이어를 생산하는 것이 아니라 외부에서 생산된 타이어를 자동차에 장착하는 작업이 주입입니다.

지난 강좌를 통해 의존성이란 무엇인지를 살펴보았습니다.

지난 강좌에서 의존성을 해결한 시퀀스 다이어그램 다시 한 번 살펴보죠.
Car 객체가 Tire 를 직접 생산하는, 즉 Tire 에 대한 의존성을 자체적으로 해결하는 방식이었습니다.

이번에는 tire 객체를 생산하는 부분을 car 객체 내부에서 하지 않고 외부에서 생산된 tire 객체를 car 객체의 생성자 인자로 주입(장착)하는 형태로 구현할 겁니다.
새로워진 시퀀스 다이어그램입니다.

이에 맞추어 클래스 다이어그램도 바뀌겠죠.
새로워진 클래스 다이어그램입니다.

이전 강좌의 클래스 다이어그램과 비교해서 달라진 곳이 보이시나요?
바로 Car 의 생성자 메서드에 인자가 들어갔다는 것입니다.  나머지는 달라진 부분이 없네요.
클래스 다이어그램과 시퀀스 다이어그램을 같이 그리는 이유가 짐작이 되시나요?
실무에서도 이 두 개의 다이어그램이 가장 중요하게 사용되고 있습니다.
더해서 개별 메서드의 작동 방식은 활동 다이어그램으로 표기하게 되는데 이는 기존의 순서도를 대체한다고 보시면 됩니다.  활동 다이어그램까지 그리기에는 게으름이..^^
그리고 코드가 스스로 말하게 하라는 말이 있죠. 
정말 의사소통이 필요하거나 그 논리가 복잡한 메서드 정도만 활동 다이어그램을 그리게 됩니다.
UML 다이어그램들에 대해서 더 공부해 보고 싶은 분은 "UML 실전에서는 이것만 쓴다. - 로버트 C 마틴 저, 이용원 역 / 인사이트" 를 추천합니다.  자자 다시 우리의 핵심 관심 사항으로 돌아가겠습니다.

이제 java 코드를 보도록 하죠.
기존 소스를 보존하기 위해서 expert001_02 패키지를 추가하고 그 아래 java 소스를 작성했습니다.

Tire.java - 이전 강좌 코드와 동일합니다.

package expert001_02;

public interface Tire {
 String getBrand();
}

KoreaTire.java - 이전 강좌 코드와 동일합니다.

package expert001_02;

public class KoreaTire implements Tire {
 public String getBrand() {
  return "코리아 타이어";
 }
}

AmericaTire.java - 이전 강좌 코드와 동일합니다.

package expert001_02;

public class AmericaTire implements Tire {
 public String getBrand() {
  return "미쿡 타이어";
 }
}

Car.java - 생성자 부분이 달라졌습니다. new 가 사라지고 생성자에 인자가 추가된 것에 주목해 주세요.

package expert001_02;

public class Car {
 Tire tire;

 public Car(Tire tire) {
  this.tire = tire;
 }


 public String getTireBrand() {
  return "장착된 타이어: " + tire.getBrand();
 }
}

Driver.java - 타이어에 대한 new 가 옮겨져 왔네요. 그리고 Car 생성자에 인수로 tire 를 넘기고 있습니다.

package expert001_02;

public class Driver {
 public static void main(String[] args) {
  Tire tire = new KoreaTire();
  //Tire tire = new AmericaTire();
  Car car = new Car(tire);

  System.out.println(car.getTireBrand());
 }
}

이러한 구현 방식은 어떤 장점을 가지게 될까요?
기존 코드에서는 Car 가 구체적으로 KoreaTire 를 생산할지 AmericaTire 를 생산할지를 결정했었습니다.  그러한 코드를 유연성이 떨어진다고 합니다.  유연성이 떨어진다는 것에 대해서는 토비의 스프링의 초난감 DAO 부분을 읽어주세요(시간이 날 때 따로 정리해 보겠습니다.  하나하나 설명하면 강좌가 너무 길어지고 핵심을 놓칠 수 있기에...) 

현실세계의 비유로 하자면 자동차가 자신이 생산될 때 스스로 어떤 타이어를 생산/장착할까를 고민하지 않고 운전자가 차량을 생산할 때, 운전자가 어떤 타이어를 장착할까를 고민하게 하는 것입니다.  자동차는 어떤 타이어를 장착할까 더 이상 고민하지 않아도 됩니다.

/src/main/java/expert001-02/Driver.java 의 context menu 에서 Run as > Java Application 을 해 보시면 이전과 동일한 결과를 보실 수 있습니다.

역시 /src/test/java/expert001_02/CarTest.java 를 만들어 보겠습니다.

package expert001_02;

import static org.junit.Assert.*;
import org.junit.Test;

public class CarTest {
 @Test
 public void 자동차_코리아타이어_장착_타이어브랜드_테스트() {
  Tire tire1 = new KoreaTire();
  Car car1 = new Car(tire1);

  assertEquals("장착된 타이어: 코리아 타이어", car1.getTireBrand());
 }

 @Test
 public void 자동차_미쿡타이어_장착_타이어브랜드_테스트() {
  Tire tire2 = new AmericaTire();
  Car car2 = new Car(tire2);

  assertEquals("장착된 타이어: 미쿡 타이어", car2.getTireBrand());
 }
}

역시 /src/test/java/expert001_02/CarTest.java 의 context menu 에서 Run as > JUnit Test 를 선택해 보겠습니다.

2 개의 테스트가 성공적으로 완료되었음을 보실 수 있습니다.

ExpertSpring30 의 context menu 에서 Run as > JUnit Test 를 선택해 보세요.
/src/test/java/expert001_01/CarTest.java
/src/test/java/expert001_02/CarTest.java
두 개의 테스트가 한 번에 실행되는 것을 보실 수 있습니다.  설명을 위해 계속 Driver.java 를 만들고 있지만 사실 테스트는 이렇게 JUnit 을 통해 진행하시는 것이 유리합니다.

자 그럼 여기서 결론입니다.  과연 이런 방식의 코드 작성은 어떤 이점이 있을까요?
기존 방식에서라면 Car 는 KoreaTire, AmericaTire 에 대해 정확히 알고 있어야만 그에 해당하는 객체를 생성할 수 있었습니다.  그러나 이렇게 의존성 주입을 통하게 되면 Car 는 그저 Tire 인터페이스를 구현한 어떤 객체가 들어오기만 하면 정상 작동하게 됩니다.  그렇게 되면 확장성이 좋아지게 된다고 하는데 후에 ChinaTire, JapenTire, EnglandTire 등등 어떤 새로운 타이어 브랜드가 생겨도 각 타이어 브랜드들이 Tire 인터페이스를 구현하기만 했다면 Car.java 소스의 변경 없이(결국 다시 컴파일 할 필요가 없다는 말이겠죠) 사용할 수 있다는 것입니다.

만약 이걸 제품화 한다면 Car.java, Tire.java 를 하나의 모듈로, Driver.java 와 KoreaTire.java, AmericaTire.java 를 각각 하나의 모듈로 만들면 나중에 새로운 ChinaTire.java 가 생긴 다해도  Driver.java, ChinaTire.java 만 컴파일해서 배포하게 되면 다른 코드는 재컴파일/재배포할 필요가 없어집니다.  지금은 단순히 Car.java, Tire.java 지만 실제 제품화하게 되면 더 많은 코드들이 재배포할 필요가 없도록 구성되어야만 코드 재컴파일/재배포에 대한 부담을 덜 수 있게 됩니다.

이것은 인터페이스를 구현(준수)했기에 얻는 이점이라고 보시면 됩니다.  현실 세계에서는 인터페이스라는 말보다 표준화 했다는 말이 더욱 와 닿으실 겁니다.  대표적인 표준화 사례는 PET 병마개를 생각하시면 됩니다.  혹시 냉장고에 여러 회사의 음료수 PET 병이 있다면 서로 뚜껑을 바꾸어서 닫아 보세요.  잘 맞을 겁니다.  바로 표준화된 규격에 맞추어 생산했기 때문이죠.

현실세계 표준 규격 준수 = 프로그래밍 세계의 인터페이스 구현

이해 안 되세요?  이해 안 되시면 오백원... ^^;

이렇게 해서 생성자를 통한 의존성 주입에 대해 살펴봤습니다.
다음 시간에는 속성을 통한 의존성 주입을 살펴보도록 하겠습니다.

그럼 다음 시간에... 호산나

반응형