스프링 IoC 와 DI
스프링을 공부하기 시작하면 가장 먼저 접하게 되는 개념인 IoC와 DI는 객체지향의 SOLID원칙 그리고 GoF의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴이다.
좀 더 자세하게 구분해 보자면 IoC는 설계 원칙에 해당하고 DI는 디자인 패턴에 해당한다.
IoC(Inversion of Control : 제어의 역전)
제어의 역전이란?
우선, 아래의 코드를 보면 Consumer가 직접 Food를 만들어 먹었기 때문에 새로 Food를 만들려면
추가적인 요리준비(코드변경)가 불가피했다. 따라서 이때는 제어의 흐름이 Consumer -> Food 로 흐르는데,
(코드변경이 불가피하기때문에 이는 강한 결합도를 가진다고 할 수있다.)
*강한 결합
객체간의 결합이 강하여 수정이 필요할 때 계속해서 내부의 코드를 수정해줘야 하는 문제가 발생,
하지만 상속과 다형성의 원리를 이용하여 이 강한 결합을 해결할 수 있다.
이를 해결하기 위해 만들어진 Food를 Consumer에게 전달해주는 식으로 변경함으로써 Consumer는 추가적인 요리준비(코드변경)없이 어느 Food가 되었던지 전부 먹을 수 있게 되었다.
소비자는 요리를 완성하고 -> 그 음식을 먹는다 Consumer -> Food (X)
요리가 완성이 되고 -> 소비자는 그 음식을 먹는다. Food -> Consumer (O)
결과적으로 제어의 흐름이 Food -> Consumer로 역전되었다.
DI(Dependency Injection : 의존성 주입)
의존성이란?
예를들어, 우리가 다리를 다쳐서 목발을 사용하여 걷게 된다면 우리는 걷기 위해 목발에 의존하고 있는 것, 즉 우리는 목발에 존성을 두게 되었다고 할 수 있다.
주입이란?
우리가 주사기를 통해 백신을 우리 몸속에 주입하듯이
여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것이다.
주입에는 3가지 방법이 있다.
1. 필드주입
아래 코드를 보면 Food인터페이스를 main메서드 안에서
필드(consumer.food)에 접근하여 new Chicken, new Pizza로 주입을 하고있다.
2. 메서드 주입
메인 메서드를 다시 보자, 이번엔 필드(consumer.food)에 접근하는 것이 아닌 메서드(setFood)를 정의하여 이를 사용하여 주입하고 있다.
3. 생성자 주입
우리가 가장 많이 사용하게 될 생성자 주입이다. Consumer객체가 생성될때 생성자에 바로 주입을 하는 방법이다.
DI(Dependency Injection)는 의존성을 객체 내부에서 직접 생성하지 않고, 외부에서 주입받는 방식이다. DI를 사용하면 객체 간의 결합도를 낮추고 유연한 코드를 작성할 수 있다.
왜 생성자주입을 많이 사용할까?
1. 의존성을 강제할 수 있다 : 생성자 주입은 객체를 생성하는 시점에 바로 의존성을 필수적으로 요구한다. 이는 컴파일 시점에서 의존성의 누락 여부를 확인할 수 있게 해준다. 필드 주입과 메서드 주입은 의존성을 선택적으로 주입받을 수 있기 때문에 누락될 가능성이 있다.
2. 불변성을 보장 : 생성자 주입을 사용하면 한 번 주입된 의존성은 변경할 수 없는 불변성(Immutability)을 가진다. 필드 주입과 메서드 주입은 의존성이 필요한 시점에 나중에 주입받을 수 있기 때문에 의존성이 변경될 수 있다. 이는 객체의 일관성과 안정성을 유지하기 어렵게 만들 수 있다.
예를들어, 다른 개발자가 메서드 주입이나 필드주입으로 의도하지 않은 주입을 할 수도 있다. 하지만 생성자 주입은 생성될 때 자동으로 주입되므로 수정을 할 수가 없다.
3. 객체의 완전성을 보장한다 : 생성자 주입은 해당 객체가 사용될 때 모든 의존성이 주입되었음을 보장한다. 즉, 생성자 주입을 통해 객체를 생성하면 완전한 상태의 객체를 얻을 수 있다. 필드 주입과 메서드 주입은 의존성이 나중에 주입될 수 있으므로 객체를 사용하기 전에 완전한 상태인지 확인해야 한다.
4. 테스트 용이성을 높인다 : 생성자 주입을 사용하면 의존성을 외부에서 주입받기 때문에 테스트 작성이 용이해진다. 테스트 코드에서는 모의 객체(Mock)나 가짜 객체(Fake)를 주입하여 테스트를 수행할 수 있다. 필드 주입과 메서드 주입은 객체 내부에서 직접 의존성을 생성하므로 테스트 작성이 어려워질 수 있다.
따라서 생성자 주입은 의존성을 강제하고, 불변성과 완전성을 보장하며, 테스트 용이성을 높이는 등의 장점으로 인해 DI를 구현할 때 가장 자주 사용되는 방법이다.