[SpringBoot] DI 와 IoC란 무엇인가

 

DI 와 IoC

 

DI / IoC

 

지난 시간에는 SOLID 원칙에 대해 알아봤다.

 

 

[객체지향] SOLID 원칙이란?

🌳 SOLID 원칙 오늘은 객체지향 개발의 5가지 원칙인 SOLID 원칙에 대해 공부해보았다. S,O,L,I,D 는 각각 단어의 첫 글자이며 합하여 솔리드 원칙이라고 부른다. 지금부터 솔리드 원칙에 대해 알아보

pgmjun.tistory.com

 

SOLID원칙은 좋은 객체지향 프로그램을 만들기 위한 중요한 원칙이다.

하지만 스프링 없이 일반적으로 개발하게 된다면 DIP와 OCP 원칙을 어기게 될 뿐만 아니라, 원칙에 맞게 코드를 끼워 맞추다 보면 실제 비즈니스 로직보다 DIP, OCP를 위한 코드가 훨씬 많아지는 일이 발생한다.

 

이때 실질적인 해결책이 바로 DI와 IoC라는 개념이다. 

 

 


 

IoC (Inversion of Control)

제어의 역전

 

 

제어의 역전은 프로그램의 제어 흐름을 개발자가 아닌 프로그램이 가져가도록 하는 것이다.

 

위 코드는 추상화에 의존해야한다는 DIP(Dependency Inversion Principle) 원칙을 지키지 않고 구현된 코드이다.

 

MemberRepository와 DiscountPolicy 모두 추상화가 아닌 구현 객체에 각각 의존하고 있다.

 

이는 제어권을 개발자가 가지고 있어 발생하는 문제이다.

 

 

만약 제어권을 개발자가 아닌 외부에 넘겨주게 된다면, 위처럼 추상화에 의존해야한다는 DIP 원칙을 깨버리는 상황을 방지할 수 있다.

 

예를 들어 할인 정책이 고정 할인 정책과 가격에 따른 할인 정책이 있는 경우, 위 코드 처럼 고정 할인 정책을 사용한다고 가정하자.

 

이럴 때 제어권을 가진 IoC 컨테이너(DI 컨테이너와 같은 맥락) 에 DiscountPolicy의 구현체를 FixDiscountPolicy로 사용하겠다고 등록만 해둔다면, 구현체에 신경쓰지 않고 추상화만으로 코드를 작성할 수 있도록 도와준다.

 

 

 이렇듯 프로그램의 제어 흐름을 개발자가 아닌 외부에서 관리하도록 하는 것을 제어의 역전(IoC : Inversion Of Control)이라고 한다.

 

 


 

DI (Dependency Injection)

의존관계 주입

 

 

의존관계 주입은 "실행 시점"에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서

클라이언트와 서버의 실제 의존관계가 연결 되는 것을 뜻한다.

 

 

DI를 사용하면

 

- 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 인스턴스를 변경할 수 있다.

 

- *정적인 클래스 의존관계를 변경하지 않고, *동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

 

정적인 클래스 의존관계
동적인 객체 인스턴스 의존관계

 

 


 

DI 컨테이너 , IoC 컨테이너

 

 

위에서 설명한 DI를 진행해주는 곳을 우리는 DI컨테이너 또는 IoC컨테이너라고 한다.

 

 대표적인 예시로 AppConfig가 있다.

AppConfig

이 코드는 스프링이 적용되지 않고 자바만을 사용하여 만들어진 AppConfig 이다.

 

AppConfig는 애플리케이션의 의존관계를 연결하는 역할을 가진 클래스이다. AppConfig에서 이처럼 의존관계를 모두 설정하게 된다면 비즈니스 로직의 변경없이 AppConfig만 수정하면 의존관계를 변경할 수 있게되며, 

 

기존 OrderServiceImpl

구체화에 의존하던 기존 OrderServiceImple을 

 

public class OrderServiceImpl implements OrderService {
	private final MemberRepositoy memberRepository;
    private final DiscountPolicy discountPolicy;
    
    public OrderService(MemberRepositoy memberRepository, DiscountPolicy discountPolicy) {
    	this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

위 코드와 같이, 추상화에 의존하여 사용할 수 있도록 해준다.

 

이렇게 구현하면 OrderServiceImpl 객체를 사용할 때, AppConfig의 memberService() 메서드를 통해 의존관계가 연결된 구현체를 가져와 사용할 수 있다.

 

이렇듯 의존관계를 주입하고 연결해주는 것을 DI컨테이너 또는 IoC 컨테이너라고 한다.

 

여기서 스프링의 개념이 들어가면 구현이 더욱 쉬워진다.

 

 

스프링 컨테이너

 

 

스프링 사용하게 되면 @Configuration 어노테이션을 사용하여 DI컨테이너와 같은 역할을 하는 스프링 컨테이너를 등록할 수 있고, @Bean 어노테이션은 의존 관계가 연결된 객체를 스프링 빈으로 등록해준다.

 

 

제일 중요한 포인트는 위처럼 AppConfig를 통해 의존관계를 직접 코드로 작성해주지 않고도 스프링 컨테이너에 Bean을 등록할 수 있다!

 

@Component 어노테이션을 통해 객체를 스프링 Bean으로 등록할 수 있으며

 

Bean에 등록된 각 객체는, 생성자를 통해 인터페이스에 구현체를 주입해준다.

 

스프링 컨테이너가 제어권을 가져가게 되므로 똑같이 DIP(Dependency Inversion Principle)도 성립하게 된다.

 

 

인터페이스 A에 대한 구현 객체가 A1,A2 로 두개라면

 

사용할 구현체에 @Primary 어노테이션만 붙여주면 의존관계를 주입할 때, 해당 객체를 넣어준다.

 

 

결론적으로 스프링 컨테이너에 의존관계를 빈으로 등록하여 사용하면, DI를 통해 추상화에 구현 객체가 주입되어 DIPOCP를 지키며 개발할 수 있게 된다.

 

 

이렇게 해서 스프링, DI, IoC를 통해 DIP, OCP에 위반되는 내용까지 깔끔하게 해결할 수 있다.