제어의 역전, 의존성 주입, 스프링 컨테이너, Bean 등 백엔드 포지션을 준비하는 사람들이라면 한번 쯤 들어봤을만한 용어들이다. 오늘은 소프트웨어 설계 5원칙을 유지하게하는 DI 컨테이너, 의존성 주입에 대해 알아보려 한다.
의존관계 주입(Dependency Injection) 의 약자로 의존 관계를 외부에서 주입하는 모양으로 프로그램이 설계되어, 의존관계 주입이라고 한다. 즉, 쉽게 말해 구현체(Class)에서 객체 인스턴스들이 어떤 구현체로 생성될 지 구현체 내부에서 결정되는 것이 아닌 '외부'에서 결정하는 것이다.
아래 코드에서 예시를 들어보자




위의 코드는 Car 인터페이스를 상속한 두개의 자동차 클래스와 운전자 클래스를 구현한 코드이다.
운전자 클래스를 보면 Driving 메소드와 Parking 메소드 뿐 아니라 차까지 직접 구현한 것을 볼 수 있다. 즉, 운전자가 Car 인터페이스 뿐 아니라 Gv70이라는 구체적인 구현체도 알아야한다.
만약 시스템 변경으로 우리가 차를 변경하게 된다면 운전자 객체에서 직접 구현체까지 변경을 해야한다. 이는 SOLID 원칙 중 OCP 원칙을 위배 될 뿐 아니라 DIP 원칙까지 위배하게 된다.
그렇다면 우리는 어떻게 SOLID 원칙을 고수하면서 객체지향 프로그래밍을 할 수 있을까?
답은 관심사의 분리에 있다.
애플리케이션을 하나의 공연이라 생각해보자. 각각의 인터페이스를 배역(배우)라 생각하자!
우리가 생각하는 상식은 배우의 캐스팅은 PD 또는 감독이 한다. 허나 위의 코드를 다시 보자
누가 차를 캐스팅하는가? 운전자 배역이 결정한다! 배우가 연기도하면서 캐스팅도 하는 꼴이 되었다,,!
해결방법은 무엇일까? 여기서 DI 가 나오게 된다.



AppConfig 라는 캐스팅을담당하는 클래스를 생성해준다. 이때 k5를 생성하는 Car 생성자를 하나 만들어준다.
Driver 클래스에서 직접 구현한 K5를 주석처리하고 Driver 생성자에 car 인터페이스를 넣은 생성자를 만들어서 안에서 car를 AppConfig에서 구현한 k5를 생성하는 appconfig.car()로 구현한다.
main 메소드에서 appconfig 객체와 appconfig.car()로 만들어진 car 객체를 생성한 뒤 Driver 객체의 me에 car를 넣어서 Driver 클래스에 구현되어있는 Driving 메소드와 Parking 메소드를 사용하여 운전한다.
자세히 보면 그냥 더 귀찮게 클래스를 하나 더만든것이 아닌가? 라고 생각할 수 있다. 하지만 외부에서 의존성을 주입해줌으로써 더욱더 쉽고 간결하고 안전하게 변경이 가능하다.

정리해보면 AppConfig가 캐스팅 디렉터 역할을 하기 때문에 배우들(클래스들)은 더이상 자신의 역할을 넘어서는 역할을 감당하지 않아도 된다,,! 캐스팅에 관해서는 몰라도 되고 그저 연기만 하면 된다!
예시 코드라서 너무 간결한 예시라 실감이 많이 안날 수 있지만 코드가 복잡해지고 구현해야하는 클래스가 많아지면 많아질 수록 이 DI는 빛난다.
위의 예시는 스프링을 사용하지 않고 DI를 구현한거고 스프링에서 위와 같이 의존 관계를 주입하는 것을 DI 컨테이너, IOC 컨테이너 등으로 부른다. 그래서 Spring 컨테이너를 DI 컨테이너라고 부른다. DI 컨테이너는 일반적으로 객체가 능동적으로 사용할 클래스를 결정하고, 언제 인스턴스를 생성할 지 결정한다.