[번역] 제어 역전 컨테이너와 의존성 주입 패턴 (2/4)

[번역] 제어 역전 컨테이너와 의존성 주입 패턴 [1][2][3][4]


의존성 주입의 여러 유형

의존성 주입의 기본 착상은 조립을 담당하는 객체를 따로 두는 것입니다. 이 객체는 MovieLister 클래스의 필드에 MovieFineter 인터페이스의 구현물 중 적당한 것을 넣어 줍니다. 결국, 의존 관계는 그림 2처럼 됩니다.


그림 2 : 의존성 주입기에서의 의존 관계

의존성 주입은 크게 세 가지 유형으로 구분할 수 있습니다. 이 세 가지를 생성자 주입, 변경자(setter) 주입,  인터페이스 주입으로 부르겠습니다. 제어 역전에 대해 요즘 진행되는 토론을 읽어보면 사람들은 이 유형들에 대해서 인터페이스 주입은 유형 1, 변경자 주입은 유형 2, 생성자 주입은 유형 3이라고 칭합니다. 숫자로 된 이름은 기억하기 어려워서 저는 늘 이 이름을 사용합니다.

피코 컨테이너(PicoContainer)의 생성자 주입

피코 컨테이너라고 하는 경량 컨테이너를 써서 주입이 어떻게 되는지 보는 것으로 시작하겠습니다. 대부분 전 피코 컨테이너부터 시작하는데 쏘트웍스(ThoughtWorks)의 제 동료 중 몇몇이 피코 컨테이너의 개발에 무척 활동적으로 참여하고 있기 때문입니다. (예. 일종의 회사 연고주의가 맞습니다.)

피코 컨테이너는 목록 클래스에 MovieFinder 구현을 어떻게 주입할지 결정하기 위해 생성자를 사용합니다. 이것이 작동하려면 영화 MovieLister 클래스는 주입 받아야 할 모든 것이 포함된 생성자를 선언해야 합니다.
class MovieLister...
public MovieLister(MovieFinder finder) {
this.finder = finder;
}
MovieFinder 구현물도 피코 컨테이너로 관리 될 것이며 역시 컨테이너에 의해 텍스트 파일의 이름을 주입 받을 것입니다.
class ColonMovieFinder...
public ColonMovieFinder(String filename) {
this.filename = filename;
}
이제는 피코 컨테이너에 각 인터페이스마다 어떤 구현 클래스를 관련지을지, 그리고 어떤 문자열을 찾기 객체에 주입할지 알려줘야 합니다.
    private MutablePicoContainer configureContainer() {
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams = {new ConstantParameter("movies1.txt")};
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation(MovieLister.class);
return pico;
}
이 설정 코드는 보통 별도 클래스에 만들어 넣습니다. 우리 예로 보면 MovieLister를 쓰기 원하는 친구들 각각 자신의 설정 클래스에 적당한 설정 코드를 만들어 넣을 겁니다. 물론, 보통은 이런 설정 정보 같은 것을 소스 코드 보다는 별도 설정 파일에 보관합니다. 클래스 하나를 만들어서 설정 파일을 읽고 그에 따라 컨테이너를 구성하도록 할 수도 있습니다. 피코 컨테이너가 비록 이런 기능을 가지고 있지는 않지만 나노 컨테이너(NanoContainer)라는 이웃 프로젝트가 있습니다. 나노 컨테이너는 피코 컨테이너를 확장해서 설정을 XML 파일로 할 수 있도록 합니다. 나노 컨테이너는 XML을 읽어서 내장하고 있는 피코 컨테이너를 설정합니다. 이 프로젝트는 메카니즘과 설정 파일 포멧을 분리하자는 철학을 가지고 있습니다.

컨테이너를 쓰려면 다음과 같은 코드를 작성하십시오.
    public void testWithPico() {
MutablePicoContainer pico = configureContainer();
MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
비록 예제에서 생성자 주입을 사용했지만 피코 컨테이너는 변경자 주입도 지원합니다. 개발자들은 생성자 주입을 선호하지만 말입니다.

스프링의 변경자(setter) 주입

스프링 프레임워크는 폭넓은 기업용 자바 개발 프레임워크입니다. 스프링은 트랜잭션과 영속화 프레임워크들과 웹 애플리케이션 개발과 JDBC에 해당하는 추상화 계층을 가지고 있습니다. 피코 컨테이너처럼 스프링도 생성자 주입과 변경자 주입을 모두 지원하지만, 스프링 개발자들이 변경자 주입을 선호하는 경향이 있기 때문에 우리 예제에서는 변경자 주입의 적당한 예로 스프링을 선택하였습니다.

영화 목록 객체가 주입을 받으려면 서비스를 받는 설정 메소드를 만들어야 합니다.
class MovieLister...
private MovieFinder finder;
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
마찮가지로 파일명을 설정하는 메소드도 만듭니다.
class ColonMovieFinder...
public void setFilename(String filename) {
this.filename = filename;
}
세 번째로 이 파일들을 묶는 설정을 구성합니다. 스프링은 XML 파일로 설정을 할 수도 있고 코드로 할 수도 있지만 보통 XML을 사용합니다.
    <beans>
<bean id="MovieLister" class="spring.MovieLister">
<property name="finder">
<ref local="MovieFinder"/>
</property>
</bean>
<bean id="MovieFinder" class="spring.ColonMovieFinder">
<property name="filename">
<value>movies1.txt</value>
</property>
</bean>
테스트 코드는 다음과 같습니다.
    public void testWithSpring() throws Exception {
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
인터페이스 주입

세 번째 주입 기술은 주입에 쓸 인터페이스를 정의하고 사용합니다. 아발론이 이 기술을 적절히 사용하는 프레임워크의 실례입니다. 나중에 아발론을 더 얘기할 것이고 여기서는 좀 단순한 예제 코드로 주입 방법을 사용해보려고 합니다.

이 기법에서는 주입하는 동안 사용할 인터페이스를 먼저 정의합니다. 여기에 영화 찾기 객체를 다른 객체에 주입하는 인터페이스가 있습니다.
public interface InjectFinder {
void injectFinder(MovieFinder finder);
}
이 인터페이스는 MovieFinder 인터페이스를 제공하는 누군가가 정의할 것입니다. 어떤 클래스라도 (예로 든 MovieLister 같이) MovieFinder를 쓰기 원한다면 이 인터페이스를 구현해야 합니다.
class MovieLister implements InjectFinder...
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}
MovieFinder 구현물에도 파일 이름을 주입할 때에 같은 방식을 씁니다.
public interface InjectFinderFilename {
void injectFilename (String filename);
}
class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
public void injectFilename(String filename) {
this.filename = filename;
}
이제 구현물들을 하나로 엮을 설정 코드를 만들 차례입니다. 단순성을 유지하려고 코드로 작성하겠습니다.
class Tester...
private Container container;

private void configureContainer() {
container = new Container();
registerComponents();
registerInjectors();
container.start();
}
설정은 두 단계로 되어 있습니다. 검색용 키로 컴포넌트를 등록하는 건 다른 예제들과 아주 비슷합니다.
class Tester...
private void registerComponents() {
container.registerComponent("MovieLister", MovieLister.class);
container.registerComponent("MovieFinder", ColonMovieFinder.class);
}
여기에 의존하는 컴포넌트를 주입할 주입기를 등록하는 단계가 추가됩니다. 주입 인터페이스마다 의존하는 객체를 주입하는 코드가 뭔가 있어야 합니다. 여기서는 컨테이너에 주입기 객체를 등록하는 것으로 처리해보겠습니다. 각 주입기 객체는 injector 인터페이스를 구현합니다.
class Tester...
private void registerInjectors() {
container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));
container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
}
public interface Injector {
public void inject(Object target);

}
의존 대상 객체가 이 컨테이너에 쓰도록 만들어진 클래스라면 이 컴포넌트가 injector 인터페이스를 스스로 구현하는 것이 타당합니다. 여기서는 영화 찾기 객체에 이것을 적용하려 합니다. 문자열 같은 범용 클래스는 설정 코드 안에서 내부 클래스(inner class)를 씁니다.
class ColonMovieFinder implements Injector......
public void inject(Object target) {
((InjectFinder) target).injectFinder(this);
}
class Tester...
public static class FinderFilenameInjector implements Injector {
public void inject(Object target) {
((InjectFinderFilename)target).injectFilename("movies1.txt");
}
}
테스트는 이 컨테이너를 씁니다.
class IfaceTester...
public void testIface() {
configureContainer();
MovieLister lister = (MovieLister)container.lookup("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
이 컨테이너는 선언된 인터페이스를 써서 의존성을 파악하며 해당하는 의존성을 주입할 때에는 주입기를 씁니다. (여기에서 사용한 컨테이너가 어떻게 구현되었는지는 이 기술에서 중요한 것이 아니 기도하고 조롱받기만 할 것이라 보여주지 않겠습니다.)

[번역] 제어 역전 컨테이너와 의존성 주입 패턴 [1][2][3][4]


이 글과 관련있는 글을 자동검색한 결과입니다 [?]

by 박성철 | 2009/10/10 17:14 | 프로그래밍 이야기 | 트랙백 | 덧글(0)

트랙백 주소 : http://gyumee.egloos.com/tb/2581046
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶