의존성 주입(DI,Dependency Injection)
한 객체가 다른 객체의 의존성(즉, 필요로 하는 객체나 서비스)을 외부에서 제공받는 디자인 패턴
. 의존성 주입을 사용하면 객체는 자신이 의존하는 객체를 직접 생성하지 않고, 외부로부터 주입받게 된다. 이로써 객체 간의 결합도가 낮아지며, 코드의 재사용성과 테스트 용이성이 향상
된다."의존한다"
라는 말의 의미는? A가 B에 의존한다. =
B가 변하면 A에 영향을 미치는 관계 = A → B
를 의미한다.
의존한다의 자바 코드 예시 |
---|
자바 - Project의 DI를 적용하지 않은 예시
package programmers;
class BackendDeveloper {
public void writeJava() {
System.out.println("자바가 좋아");
}
}
class FrontendDeveloper {
public void writeJavascript() {
System.out.println("자바스크립트가 좋아");
}
}
public class Project {
private final BackendDeveloper backendDeveloper;
private final FrontendDeveloper frontendDeveloper;
public Project(BackendDeveloper backendDeveloper, FrontendDeveloper frontendDeveloper) {
this.backendDeveloper = backendDeveloper;
this.frontendDeveloper = frontendDeveloper;
}
public void implement() {
backendDeveloper.writeJava();
frontendDeveloper.writeJavascript();
}
public static void main(String[] args) {
Project a = new Project(new BackendDeveloper(), new FrontendDeveloper());
a.implement();
}
}
자바 - Project의 DI 적용한 예시
역전
된것을 볼 수 있으며, DI를 하게 되면 의존관계 역전 원칙이 적용된다.package programmers;
import java.util.ArrayList;
import java.util.List;
interface Developer {
void develop();
}
class BackendDeveloper implements Developer {
@Override
public void develop() {
writeJava();
}
public void writeJava() {
System.out.println("자바가 좋아 - new");
}
}
class FrontendDeveloper implements Developer {
@Override
public void develop() {
writeJavascript();
}
public void writeJavascript() {
System.out.println("자바스크립트가 좋아 - new");
}
}
public class Project {
private final List<Developer> developers;
public Project(List<Developer> developers) {
this.developers = developers;
}
public void implement() {
developers.forEach(Developer :: develop);
}
public static void main(String[] args) {
List<Developer> dev = new ArrayList<>();
dev.add(new BackendDeveloper());
dev.add(new FrontendDeveloper());
Project a = new Project(dev);
a.implement();
}
}
예를 들어,
Car
객체가Engine
객체에 의존하는 경우,Car
객체는Engine
객체를 직접 생성하지 않고, 생성자, 메서드 또는 프로퍼티(Setter)를 통해 외부로부터Engine
객체를 주입 받을 수 있다.
DI 예시 2
package programmers;
interface Engine {
void start();
}
class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진이 시작됩니다.");
}
}
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
System.out.println("차량이 출발합니다.");
}
}
public class Main {
public static void main(String[] args) {
//GasolineEngine 객체를 생성하고, 이를 Car 객체에 주입
Engine engine = new GasolineEngine();
Car car = new Car(engine);
// Car 객체를 사용
car.start();
}
}
Car
객체가 Engine
객체에 의존하도록 설계한 예시이다. 여기서 Car
클래스는 Engine
인터페이스의 구현체를 필요로 하며, 구체적인 Engine
구현체는 외부에서 Car
에 주입된다.
- 인터페이스 정의 :
Engine
인터페이스를 정의하여,Car
객체가 엔진을 시작하는 기능(start
메서드)에 대한 계약을 명시한다. 이 인터페이스는Car
와 엔진 구현체 사이의 결합도를 낮춰준다.- 구현체 제공 :
GasolineEngine
클래스는Engine
인터페이스를 구현한다. 이 클래스는Engine
의start
메서드를 오버라이드하여"가솔린 엔진이 시작됩니다."
라는 메시지를 출력하는 구체적인 로직을 제공한다.- 의존성 주입 :
Main
클래스의main
메서드에서GasolineEngine
객체를 생성하고 이 객체를Car
객체의 생성자를 통해 주입한다. 이때Car
객체는Engine
인터페이스에 의존하고 있으며, 구체적인Engine
구현체는 생성 시점에 결정된다.- 의존성 사용 :
Car
객체의start
메서드를 호출하면 내부적으로 주입받은Engine
객체의start
메서드를 호출한다. 이로 인해GasolineEngine
의start
메서드가 실행되어"가솔린 엔진이 시작됩니다."
메시지가 출력된다. 이후"차량이 출발합니다."
메시지가 출력되어Car
객체의 작동을 나타낸다.
의존관계 역전 원칙(DIP, Dependency Inversion Principle)
의존관계 역전 원칙은 SOLID
원칙 중 하나로, 고수준 모듈이 저수준 모듈에 의존하지 않도록 하고, 둘 다 추상화에 의존하게 만드는 원칙이다. 여기서 고수준 모듈
이란 응용 프로그램의 핵심 비지니스 로직을 담당하는 부분
을 저수준 모듈
이란 데이터베이스 접근, 네트워크 통신
과 같은 세부적인 기능을 담당하는 부분을 의미한다.
DIP를 적용하면, 구체적인 클래스 간의 직접적인 의존성이 제거되고, 인터페이스나 추상 클래스를 통해 의존성이 관리된다. 이로써 변경에 대한 유연성이 증가하고, 모듈 간의 결합도가 낮아지며, 코드의 재사용성과 유지보수성이 향상된다.
의존관계 역전의 원칙은 아래와 같이 두 가지의 규칙을 지키는 상태를 말한다.
- 상위 모듈은 하위 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다.
- 추상화는 세부사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라져야 한다.
의존성 주입의 장 ・ 단점
장점 | 단점 |
---|---|
느슨한 결합 (Loose Coupling) | 초기 구성의 복잡성 |
유닛 테스트 용이성 | 러닝 커브 |
코드 재사용 용이성 | 오버 엔지니어링의 위험 |
프로그램의 확장성 향상 | DI 컨테이너에 대한 의존성 |
설정 및 부품 교체의 용이성 | 비용 증대 |
속성 주입이 런타임에 일어나 컴파일 시점에 종속성 주입 에러를 잡기 어려움 |