의존성 주입

소프트웨어 엔지니어링에서 의존성 주입()은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. "의존성"은 예를 들어 서비스로 사용할 수 있는 객체이다. 클라이언트가 어떤 서비스를 사용할 것인지 지정하는 대신, 클라이언트에게 무슨 서비스를 사용할 것인지를 말해주는 것이다. "주입"은 의존성(서비스)을 사용하려는 객체(클라이언트)로 전달하는 것을 의미한다. 서비스는 클라이언트 상태의 일부이다. 클라이언트가 서비스를 구축하거나 찾는 것을 허용하는 대신 클라이언트에게 서비스를 전달하는 것이 패턴의 기본 요건이다.

의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높혀준다.

의존성 주입은 광범위한 역제어 테크닉의 한 형태이다. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다. 클라이언트는 대신 서비스 제공에 대한 책임을 외부 코드(주입자)로 위임한다. 클라이언트는 주입자 코드를 호출할 수 없다. 그 다음, 주입자는 이미 존재하거나 주입자에 의해 구성되었을 서비스를 클라이언트로 주입(전달)한다. 그리고 나서 클라이언트는 서비스를 사용한다. 이는 클라이언트가 주입자와 서비스 구성 방식 또는 사용중인 실제 서비스에 대해 알 필요가 없음을 의미한다. 클라이언트는 서비스의 사용 방식을 정의하고 있는 서비스의 고유한 인터페이스에 대해서만 알면 된다. 이것은 "구성"의 책임으로부터 "사용"의 책임을 구분한다.

의도 
의존성 주입은 다음과 같은 문제를 해결한다.

 어떻게 애플리케이션이나 클래스가 객체의 생성 방식과 독립적일 수 있는가?
 어떻게 객체의 생성 방식을 분리된 구성 파일에서 지정할 수 있는가?
 어떻게 애플리케이션이 다른 구성을 지원할 수 있는가?

객체를 필요로하는 클래스 내에서 직접 객체를 생성하는 것은 클래스를 특정 객체에 커밋하는 것이고 이후에 클래스로부터 독립적으로(클래스의 수정 없이) 인스턴스의 생성을 변경하는 것이 불가능하기 때문에 유연하지 못하다. 이는 다른 객체를 필요로하는 경우 클래스를 재사용할 수 없게하며, 실제 객체를 모의 객체로 대체할 수 없기 때문에 클래스를 테스트하기 힘들게한다.

클래스는 더 이상 객체 생성에 대한 책임이 없으며, 추상 팩토리 디자인 패턴에서처럼 팩토리 객체로 생성을 위임할 필요가 없다.

아래의 UML 클래스 및 시퀀스 다이어그램을 참고.

개요 
의존성 주입은 프로그램 디자인이 결합도를 느슨하게 되도록하고 의존관계 역전 원칙과 단일 책임 원칙을 따르도록 클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리하는 것이다. 이는 클라이언트가 의존성을 찾기 위해 그들이 사용하는 시스템에 대해 알도록 하는 서비스 로케이터 패턴과 정반대되는 것이다.

의존성 주입의 기본 단위인 주입은 새롭거나 관습적인 메커니즘이 아니다. "매개변수 전달"과 동일하게 동작한다. 주입으로써 "파라미터 전달"은 클라이언트를 세부 사항과 분리하기 위해 수행되고 있는 부가적인 의미를 전달한다.

또한 주입은 전달을 제어하는 대상(클라이언트가 아님)에 대한 것이며 전달의 수행 방식-참조 또는 값 전달-과 독립적이다.

의존성 주입은 네 가지 역할을 담당하는 객체 및 인터페이스를 전제로 한다.

 사용될 서비스 객체
 사용하는 서비스에 의존하는 클라이언트 객체
 클라이언트의 서비스 사용 방법을 정의하는 인터페이스
 서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자

비유하자면,

 서비스 - 전기, 가스, 하이브리드 또는 디젤 자동차
 클라이언트 - 엔진에 상관 없이 동일하게 차를 사용하는 운전자
 인터페이스 - 운전자가 기어와 같은 엔진의 세부 사항을 이해할 필요가 없도록 보장해주는 자동변속기
 주입자 - 아이에게 어떤 차를 사줄지 결정하고 구매해준 부모

사용될 수 있는 모든 객체는 서비스로 여겨진다. 다른 객체를 사용하는 모든 객체는 클라이언트로 여겨진다. 이름은 객체가 무엇을 위한 것인지와 무관하며 한 번의 주입에서 객체가 하는 역할과 관련이 있다.

인터페이스는 클라이언트가 의존성으로 예상하는 타입이다. 쟁점은 무엇을 접근할 수 있게 하는가이다. 이는 실제로 서비스에 의해 구현된 인터페이스 타입일 수도 있지만 추상 클래스 또는 DIP를 위반하고, 테스트를 하기위한 동적으로 결합도 낮추는것을 희생하긴 하지만 concrete 서비스 자체일 수도 있다. 클라이언트가 인터페이스를 구성하거나 확장하는 것처럼 구체적으로 다룰 수 없도록 알지 못하게 하는 것이 요구된다.

클라이언트는 의존성의 특정 구현에 대한 구체적인 지식이 필요 없다. 인터페이스의 이름과 API만 알면 된다. 결과적으로 인터페이스가 변경되더라도 클라이언트는 수정할 필요가 없어진다. 하지만 인터페이스가 클래스로 존재하다가 인터페이스 타입으로(그 반대의 경우에도) 리팩토링될 경우 클라이언트는 다시 컴파일이 필요할 수 있다. 이는 클라이언트와 서비스가 독립적으로 발행될 경우 중요하다. 이런경우에 결합도가 높아지는 문제는 안타깝게도 의존성 주입으로 해결할 수 없다.

주입자는 클라이언트에게 서비스를 소개한다. 종종 클라이언트를 생성하기도 한다. 주입자는 객체를 클라이언트와 같이 처리하고 나중에 다른 클라이언트의 서비스로 처리하여 매우 복잡한 객체 그래프를 함께 연결할수도 있다. 주입자는 실제로 함께 동작하는 많은 객체가 될 수 있지만 클라이언트는 될 수 없다. 주입자는 다음과 같은 많은 이름들로 참조될 수 있다: assembler, provider, container, factory, builder, spring, construction code, or main.

의존성 주입은 모든 객체가 구성과 동작을 분리하도록 요구하는 하나의 규율로 적용될 수 있다. 구성을 수행하는 DI 프레임워크에 의지하면 new 키워드의 사용을 금지하거나 덜 엄격하게 값 객체의 직접 생성을 허용할 수 있다.

분류 
역제어(IoC)는 DI보다 더 일반적이다. 간단하게 말하자면 IoC는 호출을 요구하는 대신 다른 코드가 호출할 수 있게 함을 의미한다. DI가 없는 IoC의 한 예로 템플릿 메소드 패턴이 있다. 여기서 다형성은 서브클래싱, 즉, 상속을 통해 달성된다.

의존성 주입은 컴포지션을 통해 IoC를 구현하므로 종종 전략 패턴과 동일하지만, 전략 패턴의 의도는 객체의 수명동안 의존성을 교환할 수 있도록 하는 것이고, 의존성 주입에서는 단일 의존성 인스턴스만 사용할 수 있도록 하는 것이다. 여전히 다형성은 유지되지만 위임과 컴포지션을 통해 이루어진다.

의존성 주입 프레임워크 
CDI와 그 구현물인 Weld, Spring, Guice, Play framework, Salta, Glassfish HK2, Dagger, Managed Extensibility Framework(MEF)와 같은 애플리케이션 프레임워크는 의존성 주입을 지원하지만 필수는 아니다.

장점 

 의존성 주입은 클라이언트의 구성 가능성을 유연하게 해준다. 클라이언트의 행위는 고정되어 있다. 클라이언트는 클라이언트가 기대하는 고유한 인터페이스를 지원하는 모든 것을 할 수 있다.
 의존성 주입을 통해 시스템의 구성 세부 사항을 외부의 구성 파일에서 사용하여 리컴파일 없이 시스템을 재 구성할 수 있다. 분리된 구성은 컴포넌트의 여러 구현을 요구하는 다양한 상황을 위해 작성될 수 있다. 이는 국한되어 있지는 않지만 테스팅을 포함한다.
 의존성 주입은 코드의 동작에서의 어떠한 변경도 요구하지 않으므로 리팩터링으로써 레거시 코드에도 적용할 수 있다. 클라이언트는 더 독립적이며 테스트에 속하지 않은 다른 객체를 가장하는 stubs 또는 모의 객체를 사용해 독립된 유닛 테스트가 더 쉬워지는 결과를 얻는다.
 의존성 주입을 통해 클라이언트는 사용해야하는 모든 구체적인 구현에 대한 지식을 제거할 수 있다. 디자인 변경이나 결함의 영향으로부터 클라이언트를 독립하는데 도움을 주며, 이는 재사용성, 테스트가능성, 유지가능성을 향상시킨다.

구조

UML 클래스 및 시퀀스 다이어그램

의존성 주입의 이점 
 의존 관계 설정이 컴파일시가 아닌 실행시에 이루어져 모듈들간의 결합도를 낮출 수 있다.
 코드 재사용을 높여서 작성된 모듈을 여러 곳에서 소스코드의 수정 없이 사용할 수 있다.
 모의 객체 등을 이용한 단위 테스트의 편의성을 높여준다.

적용 유형 
마틴 파울러는 다음과 같은 세 가지의 의존성 주입 패턴을 제시하였다.

 생성자 주입 : 필요한 의존성을 모두 포함하는 클래스의 생성자를 만들고 그 생성자를 통해 의존성을 주입한다.
 세터(Setter)를 통한 주입 : 의존성을 입력받는 세터(Setter) 메소드를 만들고 이를 통해 의존성을 주입한다.
 인터페이스(Interface)를 통한 주입 : 의존성을 주입하는 함수를 포함한 인터페이스를 작성하고 이 인터페이스를 구현하도록 함으로써 실행시에 이를 통하여 의존성을 주입한다.

같이 보기 
 스프링 프레임워크
 구글 주스

참고 자료 

소프트웨어 구조
소프트웨어 디자인 패턴
컴포넌트 기반 소프트웨어 공학