스프링을 사용하다보면 DI와 IoC라는 단어를 듣게 되는데
이게 무엇일까?
IoC란 영어 그대로 제어의 역전이라 부른다.
프로그램의 제어 흐름을 개발자가 아니라 프레임워크가 담당하는 것을 의미한다.
객체의 생성과 호출을 프레임워크가 관리하고, 개발자는 그 객체들이 무엇을 해야 하는지에만 집중하면 된다.
간단하게 예시들 들어보자.
결제하기 위한 인퍼테이스가 아래에 처럼 구현되어 있다고 해보자.
// 결제 서비스 인터페이스
public interface PaymentService {
void processPayment();
}
// CreditCard 결제 서비스
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment() {
System.out.println("결제 방법은 CreditCard로.");
}
}
// PayPal 결제 서비스
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment() {
System.out.println("결제 방법은 paypal로.");
}
}
public class OrderService {
private PaymentService paymentService;
// 특정 결제 서비스 구현체를 직접 선택하고 생성
public OrderService() {
// IoC 적용 전: 직접 결제 서비스 선택
this.paymentService = new CreditCardPaymentService(); // CreditCard 서비스 사용
// 만약 PayPal을 사용하고 싶으면 직접 아래처럼 수정해야 함
this.paymentService = new PayPalPaymentService();
}
public void placeOrder() {
System.out.println("Order placed.");
paymentService.processPayment();
}
}
즉, IoC를 적용하지 않으면 방식이 바뀔 때 마다 직접 해당 구현체를 선택하고 코드를 수정해야한다.
public class OrderService {
private PaymentService paymentService;
// IoC 적용: 외부에서 PaymentService 객체를 주입받음
public OrderService(PaymentService paymentService) { // 결제 방식을 여기에 주입하면 됨.
this.paymentService = paymentService; // 외부에서 주입된 객체를 사용
}
public void placeOrder() {
System.out.println("Order placed.");
paymentService.processPayment(); // 주입된 객체의 메서드를 호출
}
}
즉, IoC를 적용하면 코드를 수정할 필요 없이 외부에서 주입된 객체를 이용해서 여러 방식을 쉽게 변경하거나 추가가 가능하다.
IoC는 객체간의 결합도를 낮추고, 코드의 유연성과 유지보수성을 엄청나게 높여준다.
DI란 IoC의 구체적인 구현 방식 중 하나이다. 간단히 말하자면,
필요한 객체(의존성)를 외부에서 주입받는 방식이다.
DI는 프로그램의 객체 간의 결합도를 줄이고, 유연성을 높이는 설계 패턴이다.
DI의 종류에는 세가지가 있다.
public class OrderService {
private final PaymentService paymentService;
// 생성자 주입
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
// 주문 로직 처리
paymentService.processPayment();
}
}
public class OrderService {
private PaymentService paymentService;
// 세터 주입
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment();
}
}
public class OrderService {
@Autowired // 스프링에서 사용
private PaymentService paymentService;
public void placeOrder() {
paymentService.processPayment();
}
}
요약하자면,
IoC와 DI는 객체 간의 의존성을 낮추고, 유연하고 유지보수하기 쉬운 코드를 작성할 수 있도록 도와준다.
대신 초기 설정이나 복잡성 증가 같은 단점이 있을 수 있으며, 객체 간의 관계를 추적하기 어려워 질 수도 있다.
즉, 복잡한 시스템이나 대규모 애플리케이션에서 매우 유용하며, 시스템의 유지보수성과 확장성을 크게 향상시킬 수 있는 패턴이다.