소프트웨어 엔지니어링에서 의존성 주입(dependency injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. "의존성"은 예를 들어 서비스로 사용할 수 있는 객체이다. 클라이언트가 어떤 서비스를 사용할 것인지 지정하는 대신, 클라이언트에게 무슨 서비스를 사용할 것인지를 말해주는 것이다. "주입"은 의존성(서비스)을 사용하려는 객체(클라이언트)로 전달하는 것을 의미한다. 서비스는 클라이언트 상태의 일부이다. 클라이언트가 서비스를 구축하거나 찾는 것을 허용하는 대신 클라이언트에게 서비스를 전달하는 것이 패턴의 기본 요건이다.
의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높혀준다.
의존성 주입은 광범위한 역제어 테크닉의 한 형태이다. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다. 클라이언트는 대신 서비스 제공에 대한 책임을 외부 코드(주입자)로 위임한다. 클라이언트는 주입자 코드를 호출할 수 없다. 그 다음, 주입자는 이미 존재하거나 주입자에 의해 구성되었을 서비스를 클라이언트로 주입(전달)한다. 그리고 나서 클라이언트는 서비스를 사용한다. 이는 클라이언트가 주입자와 서비스 구성 방식 또는 사용중인 실제 서비스에 대해 알 필요가 없음을 의미한다. 클라이언트는 서비스의 사용 방식을 정의하고 있는 서비스의 고유한 인터페이스에 대해서만 알면 된다. 이것은 "구성"의 책임으로부터 "사용"의 책임을 구분한다.
class Fruit {
public void fruitSection() {
System.out.println("과일 판매 코너입니다.");
}
}
class Snack {
public void snackSection() {
System.out.println("간식 판매 코너입니다.");
}
}
public class Store {
private final Fruit fruit;
private final Snack snack;
public Store(Fruit fruit, Snack snack) {
this.fruit = fruit;
this.snack = snack;
}
public void StoreGuide() {
fruit.fruitSection();
snack.snackSection();
}
public static void main(String[] args) {
Store a = new Store(new Fruit(), new Snack());
a.StoreGuide();
}
}
의존관계역전원칙 : 의존성 주입을 할 때는 의존관계원칙(Dependency Inversion Principle)이 적용된다. 이는 2가지 규칙을 지키는 상태를 일컫는다.
1.상위 모듈은 하위모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다.
2.추상화는 세부사항에 의존해서는 안 된다. 세부 사항은 추상화에 따라 달라져야 한다.
import java.util.ArrayList;
import java.util.List;
interface Product {
void goods();
}
class Fruit implements Product {
@Override
public void goods() {
fruitSection();
}
public void fruitSection() {
System.out.println("과일 판매 코너입니다.");
}
}
class Snack implements Product {
@Override
public void goods() {
snackSection();
}
public void snackSection() {
System.out.println("과일 판매 코너입니다.");
}
}
public class Store {
private final List<Product> products;
public Store(List<Product> products) {
this.products = products;
}
public void storeGuide() {
products.forEach(Product::goods);
}
public static void main(String[] args) {
List<Product> productList = new ArrayList<>();
productList.add(new Fruit());
productList.add(new Snack());
Store a = new Store(productList);
a.storeGuide();
}
}
public class Store {
@Autowired
private List<Product> products;
public class Store {
// final을 붙일 수 있다.
private final List<Product> products;
//@Autowired
public Store(List<Product> products) {
this.products = products;
}
public class Store {
private List<Product> products;
@Autowired
public void storeGuide() {
products.forEach(Product::goods);
하지만 수정자 주입이나 일반 메소드 주입을 이용하게되면 불필요하게 수정의 가능성을 열어두게 되고, 이는 OOP의 5가지 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 원칙)를 위반하게 됩니다. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋습니다. 또한, 필드 주입 방식은 null이 만들어질 가능성이 있는데, final로 선언한 생성자 주입 방식은 null이 불가능합니다.