Dependency! 우리 말로 의존(성)이라고 하는 이 용어는 무슨 뜻인가? 어떤 이는 변경에 영향을 받게 되는 성질이라고도 하고 어떤 이는 대상을 알고 있는(참조 하는) 성질이라고도 한다. 이 글에서는 '대상을 알게 된다'
에 초점을 맞춰 살펴보겠다.
내가 그의 이름을 불러주기 전에는
그는 다만
하나의 몸짓에 지나지 않았다.내가 그의 이름을 불러주었을 때,
그는 나에게로 와서
꽃이 되었다.김춘수, 꽃
김춘수의 시 '꽃'
에서는 나와 그의 관계가 이름을 매개로 이어진다. 의존도 이와 같다.
내가 그의 이름을 알지 못한다면 의존하지 않는 것이며, 내가 그의 이름을 알 때 의존하는 것이다.
어쩌면 글보다 익숙할 자바 코드로 살펴보자.
// 나는 너의 이름을 알지 못하기에 의존하지 않는다.
public class I extends Person {
}
public class You extends Person {
}
현재 클래스 I
는 클래스 You
를 알지 못하기에(참조하지 않기에) You
에 의존하지 않는다.
// 이제 나는 너의 이름을 알기에 너에게 의존한다.
public class I extends Person {
private You you
}
public class You extends Person {
}
이제 클래스 I
는 클래스 You
를 알기에(참조하기에) You
에 의존한다. 대상을 아는 행위(참조하는 행위)가 의존임을 알았으니, 이제 의존의 다른 정의인 '변경에 영향을 받는 성질'
이 결국 대상을 아는 행위에서 나오는 같은 개념임을 알아보자.
// 나는 너를 알고 인사를 건넨다. 너는 "안녕"이라 인사한다.
public class I extends Person {
private You you = new You();
public void greeting() {
you.greeting();
}
}
public class You extends Person {
public void greeting() {
system.out.println("안녕.");
}
}
나는 너에게 의존한다. 이제 너에게 인사를 건넨다. 너는 내게 "안녕."
이라 인사한다. 너는 문득 부끄럽다. 안녕 이라니, 처음 건네는 인사로서는 너무 부족했던 게 아닐까. 내 이름 정도는 밝히는 게 예의지 않을까. 하며 조금의 말을 붙이다 다시 부끄러워 말을 삼킨다.
// 너는 인사말을 바꾼다.
public class I extends Person {
private You you = new You();
public void greeting() {
you.greeting();
}
}
public class You extends Person {
public void greeting() {
System.out.println("안녕. 나는...");
}
}
네가 인사말을 바꾸자 나의 인사의 결과도 바뀌었다. 내가 너에게 의존한 결과로 너의 변경에 영향을 받은 것이다. 이제 의존의 또다른 정의인 '변경에 영향을 받는 성질'
도 보았다.
Injection! 우리 말로 주입이라고 하는 이 용어는 무엇일까? 어째서 프로그래밍에 이런 용어가 붙었을까? 주입은 외부에서 내부로 일어난다. 주입이라는 용어는 외부와 내부의 분리
를 전제한다. 무엇이 외부이고 무엇이 내부일까? 나와 너의 새로운 세계를 정의해보자.
// 나와 너는 이제 우리 속에서 사용된다.
public class World {
public void meeting() {
I i = new I();
i.gretting();
}
}
public class I extends Person {
private You you = new You();
public void greeting() {
you.greeting();
}
}
public class You extends Person {
public void greeting() {
System.out.println("안녕. 나는...");
}
}
외부에서 나에게 의존하는 존재 World(세계)
를 Client
라고 부르자. 나는 너에게 의존하지만, 세계는 그걸 알지도, 바꿀 수도 없다. 이제 세계가 내가 너에게 의존하도록 만들어주자.
// 이제 나와 너 사이의 의존을 정해주는 건 Client인 세계가 되었다.
public class World {
public void meeting() {
You you = new You();
I i = new I(you)
i.gretting();
}
}
public class I extends Person {
private You you;
public I(You you) {
this.you = you;
}
public void greeting() {
you.greeting();
}
}
public class You extends Person {
public void greeting() {
System.out.println("안녕. 나는...");
}
}
이제 나와 너의 의존을 결정해주는 건 외부인 세계가 되었다. 세계가 나의 의존관계인 너를 주입
해주고 있다. 이렇게 Client
에서 클래스에 다른 클래스를 전달하여 의존관계를 맺어주는 행위를 Dependency Injection(의존성 주입)
이라고 한다. 그렇다면 왜 DI가 필요한가?
DI가 있기 전에는 나는 항상 너에게만 의존했다.
// 나는 너에게 의존하도록 고정되어있다.
public class I extends Person {
private You you
}
public class You extends Person {
}
이는 코드상으로 고정되어(컴파일 타임에 고정) '나'가 다른 클래스에 의존하도록 변경할 경우 '나'의 코드를 수정해야 한다는 것을 의미한다. DI를 사용한다면 이야기가 달라진다. 외부인 '세계'는 '나'의 의존관계를 외부에서 얼마든지 바꿀 수 있다. '나'의 내부 코드를 바꾸지 않아도 그 대상이 사람(Person)이기만 하다면 누구라도 나와 의존관계를 맺을 수 있게 되는 것이다.
먼저 자유롭게 DI 받을 수 있도록 '나'의 코드를 바꿔보자.
// 이제 나는 세계에서 주입받은 누구에게도 의존할 수 있다.
public class I extends Person {
private Person person;
public I(Person person) {
this.person = person;
}
public void greeting() {
person.greeting();
}
}
이제 나는 세계에서 주입받은 누구에게도 의존할 수 있다. 세계는 나에게 너뿐만이 아닌 다른 누구와도 관계를 지어주고 싶어한다.
// 이제 세계는 사람(Person)이라면 누구라도 나에게 DI 해줄 수 있다.
public class World {
public void meeting() {
Person someone = new Someone();
I i = new I(someone)
i.gretting();
}
}
public class Someone extends Person {
public void greeting() {
System.out.println("안녕하세요.");
}
}
이제는 '나'의 코드를 전혀 변경하지 않아도 세계가 해주는 DI에 의해 누구와도 의존할 수 있고, 다양한 형태로 책임을 수행할 수 있게 되었다.
그러나 DI만으로는 DI를 완벽하게 이룰 수 없다. 무슨 뜻일까?
// 현재 세계 또한 자바 코드로 존재하므로 결국 의존 관계는 우리가 지어준다.
public class World {
public void meeting() {
Person someone = new Someone();
I i = new I(someone)
i.gretting();
}
}
현재 세계 또한 자바 코드로 존재하므로 결국 의존 관계는 우리가 지어준다. 결국 외부
가 우리가 작성하는 자바 코드상에 존재한다면 의존 대상을 바꿀 때마다 코드를 변경해야 한다. 이제 한층 더 나아갈 때가 되었다. 외부
를 우리가 작성하는 자바 코드인 세계보다 더 큰 경계로 확장해야 한다. 이렇게 확장된 존재를 'Framework'
라 한다. 현재는 Framework
의 기능을 자바 코드 외부에서 DI를 해주는 존재로 한정하고, 이를 'IoC Container
로 칭하자.
이전 단계에서는 우리가 직접 코드를 통해 의존 관계를 맺고, 외부와 내부를 구분하여 DI를 해주었다. 즉, 우리가 의존관계를 Control 하는 상태이다. 그러나 자바 코드 외부로 DI의 주체를 확장하면, Control의 주체가 우리가 아닌, 외부의 존재인 Framework
로 넘어간다. 이를 Inversion of Contol(제어의 역전)이라 한다.
이제 우리의 세계는 DI의 권한을 외부인 'IoC Container'
에게 주었다. 그리하여 의존 대상을 코드로 정의하지 않아도 마음껏 의존 대상을 변경할 수 있게 되었다.
// 이제 세계는 나와 내가 가지는 의존관계를 정의하지 않는다. IoC Container에게 제어를 위임했기 때문이다.
public class World {
private I i;
public void meeting() {
i.gretting();
}
}
이제 세계는 나와 내가 가지는 의존관계를 정의하지 않는다. IoC Container에게 제어를 위임했기 때문이다. 이제 '나'와 어떤 의존관계를 맺도록 변경하더라도, 세계를 포함한 자바 코드를 일절 변경하지 않아도 된다. 단순히 IoC Container의 설정만 변경하면 된다. 이렇게 코드로 의존관계가 정해지지 않고 컴파일 이후 설정에 의해 런타임에 의존관계가 정해지는 상태를 '런타임 의존관계'
라고 한다. 런타임 의존관계는 완전한 DI를 달성하기 위해 반드시 필요한 작업이며, 이를 이루기 위해 Framework의 IoC Container에게 DI 권한을 위임하는 작업이 필요했다.
결국 IoC는 완전한 DI를 이루기 위해 필요하다. DI가 자바 코드상에서 이뤄진다면 의존 관계 변경이 필요할 때마다 자바 코드를 수정해야 하기 때문이다. IoC Container에게 DI 권한을 주어(Inversion of Control, 제어의 역전) DI가 컴파일 이후에 일어나는 런타임 의존관계
를 가능케 하여 완전한 DI를 달성한다. DI가 필요한 이유는 외부의 변경에 유연한 설계를 만들기 위해서이다. 객체의 의존관계를 자유롭게 맺어주면 객체의 역할에 맞는 책임을 수행할 방법을 변경하기 쉽게 만들 수 있기 때문이다.
내 머리~