이전 포스트 Dependency Inversion Principle 에서 의존하는 모듈, 레이어 사이에 추상 (interface 또는 abstract class)을 의존함으로써 구현의 상세(detail)를 의존할 때보다 느슨한 커플링을 만들 수 있고, 구조적 설계와 비교할 때 의존의 방향이 역전 되었다라고 하며, 의존의 전이를 끊었다라고 말했습니다.
Inversion of Control 은 프레임워크가 정의한 추상(interface 또는 abstract class)을 클라이언트 코드가 구현을 하고, 구현된 객체를 프레임워크에 전달(또는 주입) , 프레임워크가 제어를 가지게 함으로써 클라이언트 코드로 부터 제어의 수를 줄이게 하는 것이라고 말할 수 있습니다. 모든 제어를 클라이언트 코드가 가지고 있는 구조적 설계와 비교해 프레임워크가 제어를 가지는 것을 제어가 역전 되었다 라고 말합니다.
Invsersion of Control Container 란 무엇일까요? Invsersion of Control 을 담고 있는 상자 일까요?
용어의 개념을 왜곡하지 않고 가능한 원래의 의미를 알기 위해 마틴 파울러의 [ Inversion of Control Containers and the Dependency Injection pattern] 의 글을 번역 인용하여 IoC Container를 설명하고자 합니다.
콜론(:) 구분자로 저장된 영화의 목록에서 특정 감독의 영화를 조회하는 예제는 아래와 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| public class MovieLister { private MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder( "Movies1.txt" ); } public List<movie> MoviesDirectedBy(String arg) { List<movie> items = new List<movie>(); List<movie> allMovies = finder.FindAll(); foreach (Movie item in allMovies) { if ( string .Equals(item.DirectorName, arg)){ items.Add(item); } } return items; } } public interface MovieFinder { List<movie> FindAll(); } public class ColonDelimitedMovieFinder:MovieFinder { string sourceFilename = string .Empty; public ColonDelimitedMovieFinder( string arg) { this .sourceFilename = arg; } #region MovieFinder 멤버 public List<movie> FindAll() { List<movie> items = new List<movie>(); //Read From file : sourceFilename; //Split with Colon Delemiter //Add to items List string rawText = File.ReadAllText(sourceFilename); foreach ( string row in rawText.Split( new char []{ ':' })) { items.Add( new Movie(row.Split( new char [] { '|' })[0], row.Split( new char [] { '|' })[1])); } return items; } #endregion } public class Movie { public string Name; public string DirectorName; public Movie( string name, string directorName) { this .Name = name; this .DirectorName = directorName; } } } </movie></movie></movie></movie></movie></movie></movie></movie> |
이 클래스를 나만 사용한다면 깔금하고 멋져보입니다. 하지만, 나의 친구가 이 멋진 기능을 자신의 프로그램에 복사해서 넣고 싶다면 어떤 일이 벌어질까요? 그들이 영화 목록을 콜론으로 구분된 "movie1.txt"라는 이름의 텍스트파일로 정한다면 환상적일 것입니다. 다른 파일이름을 사용하고 싶다해도 속성값 만 변경하면 되니 괜찮습니다. 하지만 영화목록을 위한 완전히 다른 폼을 사용하길 원한다면 : SQL 데이터베이스, XML 파일, 다른 텍스트파일 포멧 , 이 경우에 데이터를 가져오는 다른 클래스가 필요 합니다. 나는 MovieFinder interface를 사용했기 때문에 MoviesDirectedBy 메소드는 변경하지 않아도 됩니다. 하지만 알맞는 finder 인터페이스 구현 인스턴스를 얻을 방법이 필요합니다.
이 상황을 P of EAA 에서는 Plugin 이라고 기술합니다. finder를 위한 구현 클래스는 컴파일타임에 링크되지 않습니다. 내 친구가 어떤 구현을 원할지 모르니 Lister 가 특정 구현과 일하는 대신, 내 손을 떠나 나중에 플러그 될 수 있도록 합니다. 문제는 어떻게 해야 Lister가 구현 클래스를 무시하면서도 여전히 인스턴스와 일할 수 있게 하느냐 입니다.
이것을 실제 시스템으로 확장하자면, 우리는 수십개의 이런 서비스와 컴포넌트를 가지고 있을 것입니다. 이런 경우에 interface 를 통해 이야기하는 방법으로 컴포넌트 사용 방법을 추상화 할 수 있습니다. ( 컴포넌트가 interface를 가지도록 설계되지 않았다면 Adapter를 사용할 수 있습니다.)
중요한 문제는 이 플러그인들을 어떻게 어플리케이션에 조립하느냐 입니다. 이것이 경량 컨테이너 lightweight Container 가 부상하게 이유입니다. 일반적으로 Inversion of Control을 이용해 해결할 수 있습니다.
주: 여기서 경량 컨테이너란 Service Locator, PicoContainer, Spring, Avalon, Guice 등을 말합니다.
주: 여기서 Inversion of Control을 이용해 해결한다는 말은 구현을 선택하는 전략을 가진 객체에게 제어를 넘김으로써 구현을 찾도록 한다는 것을 의미합니다.
주 : 추상을 사용함으로써 커플링을 느슨하게 하는 아이디어는 로버트 C. 마틴의 Dependency Inversion Principle 이 도입된 것입니다.
Inversion of Control
이 컨테이너들이 "Inversion of Control"을 구현하고 있기 때문에 유용하다고 말할 때 완전히 혼란 스러워졌습니다. Inversion of Control 은 프레임워크의 일반적인 특성입니다. 따라서 경량 컨테이너들이 특별하다라고 말하는 것은, 내 차는 바퀴를 가지고 있기 때문에 특별하다라고 말하는 것입니다.
그렇다면 과연 컨트롤의 어떤 관점이 역전 되었다는 것일까? 내가 처음 제어의 역전을 말했을 때는 사용자 인터페이스의 제어를 의미하는 것이었습니다. 초기의 사용자 인터페이스는 어플리케이션에 의해 제어 되었습니다. 여러분은 "Enter Name" , "Enter Address" 와 같은 순차적인 명령을 가지고 있고, 여러분의 프로그램은 프롬프트를 나타낸 후 각각에 대해 응답을 선택할 것입니다. main loop를 담고 있는 그래피컬 UI 프레임워크를 가지고 작업한다면, 스크린의 다양한 필드를 위한 이벤트 핸들러가 제공되고, 프로그램의 중앙 제어는 UI 프레임워크에게 역전됩니다. 제어는 여러분에게서 프레임워크로 이동한 것이죠.
컨테이너에게 있어서 역전 이라고 하는 것은 컨테이너가 플러그인 구현을 스스로 찾는 것을 의미 합니다. 나의 순진한 Lister는직접 인스턴스화된 finder 구현을 찾습니다. 이렇게 하면 플러그인으로 사용할 수 없게 됩니다. 컨테이너를 사용하면 Lister에 구현을 주입할 수 있습니다.
심사숙고한 후 우리는 이 패턴을 위한 좀더 구체적인 이름이 필요하다는 결론에 도달했습니다. 제어의 역전 Inversion of Control은 너무 일반적인 용어여서 사람들이 매우 혼란스러어 했습니다. IoC 에 대한 다양한 토론을 거친 결과 우리는 IoC를 Dependency Injection 이라고 이름 붙이기로 했습니다.
마틴 파울러의 [ Inversion of Control Containers and the Dependency Injection pattern]중 일부
no more IoC , it`s Dependency Injection!
위의 내용을 요약하자면 :
이미 작성된 코드를 다른 어플리케이션에 재사용하거나, 의존하고 있는 객체의 행위를 컴파일타임 이후에 다른 행위로 플러그 하고자 한다면, 클라이언트 객체는 추상을 가지고 작업을 해야 합니다. 이 추상에 구현을 교체할 수 있는 패턴을 마틴파울러는 Plugin 패턴이라고 이름 붙였습니다. 그럼 추상을 구현한 다양한 플러그인 중에 알맞는 플러그인을 선택해야하는 문제가 남게 됩니다.
의존 그래프의 생성과 생성된 의존을 주입 하는 일이 코드의 여러곳에 반복적으로 난립하게 되는 문제이지요, 의존 그래프의 생성과 주입 전략을 가진 객체가 등장 했으니 이것을 경량 컨테이너 라고 하는 놈 입니다. 제어를 넘겨 받은 컨테이너 이므로 Inversion of Control Container 라는 이름을 얻게 되었습니다. 그런데, IoC Container 라는 이름이 너무 일반적인 용어라서 사람들이 너무 혼란스러워 하게 되었고, IoC Container 가 하는 일이 결국에는 의존을 주입하는 일이기 때문에 Dependency Injection 이라 부르기로 결정했습니다.
그럼 의존을 주입하는 방법이 궁금해 질 것입니다. 의존을 주입하는 방법으로는
1. Constructor Injection
2. Setter Injection
3. Interface Injection
의 크게 세가지가 있으며, 주입할 객체를 생성하는 설정을 코드에 내재하거나 설정파일(주로 XML 파일) 을 이용하기도 합니다.
이 포스트는 IoC Container 의 개념을 설명하는 목적으로 여기에서 줄이며, 주입하는 방법까지 설명은 마틴 파울러의 Inversion of Control Containers and Dependency Injection pattern 글과 이 글을 번역 설명한 행복한 아빠 님의 IoC 와 DI 에 대한 블로그 포스트 시리즈 1, 2, 3 포스트를 읽어보시기 바랍니다.
토비님의 블로그와 저서 스프링 프레임워크 3.0 은 IoC 컨테이너의 자세한 내용을 담고 있습니다.
마틴 파울러의 [Inversion of Control Containers and the Dependency Injection pattern] 글에 대한 여러 개발자님들의 번역이 있군요. 그 중 SKY-TIGER 님의 번역 글을 링크 합니다.
참고 :
Inversion of Control Containers and Dependency Injection pattern by 마틴 파울
러
댓글 없음:
댓글 쓰기