IoC와 DI는 스프링에만 국한된 단어가 아니다. IoC는 프로그래밍 원칙 중 하나이고 DI는 디자인 패턴 중 하나이다.
제어의 역전(Inversion of Control)
: 내가 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것.
말 그대로 제어권이 뒤바뀐다는 의미한다. 객체 측면에서는 객체에 대한 제어권을 애플리케이션이 가지는 게 아니라 프레임워크가 가지는 것을 의미한다.
IoC가 왜 필요한가?
기존에는 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고 연결하고 실행까지 다 했다.
한 마디로 구현 객체가 프로그램의 제어 흐름을 스스로 다 조종했다. 혼자서도 모든 걸 알아서 잘하는 스마트한 객체를 설계했다는 뜻이다. 이는 개발자의 입장에서는 자연스러운 흐름이며 프로그래머의 입장에서도 혼자 스스로 잘하는 객체가 있으면 더 좋은 게 아닌가..?
직관적이고 단순한 프로그램 스케일에서는 더 좋고 효율적으로 보일 수 있겠지만 객체의 종류가 수백수천 가지가 넘고 이들이 복잡하게 얽혀있는 프로그램에서는 혼자 뭐든지 잘하는 객체가 오히려 독이 될 수 있다. 에러가 났을 때 문제가 발생한 지점을 파악하기 어렵고 프로그램의 유연성도 방해한다.
정리하자면 IoC는 객체지향 원칙을 잘 지키기 위해서 필요하다. 애플리케이션에서 모든 객체에 대한 제어와 관리를 부담하는 것이 아니라 객체를 관리해주는 프레임워크 활용부와 애플리케이션의 구현부를 분리함으로써 역할과 관심을 분리할 수 있고 이에 따라 응집도를 높이고 결합력을 낮추어 변경에 유연한 설계가 가능해진다.
스프링에서는 AppConfig가 등장한 후 구현 객체는 자신의 로직을 실행하는 역할만 담당하고 프로그램의 제어 흐름은 AppConfig가 가지게 된다. 기존 프로그램과 비교한다면 기존에는 구현 객체만 보아도 이 객체가 전체 프로그램에서 어떤 영향을 미치고 이로 인해 연쇄적으로 어떤 작업이 이루어지는지 파악할 수 있었다면 AppConfig가 등장한 후에는 구현 객체만 보면 그 객체의 역할만 파악할 수 있고 그 객체가 전체 프로그램에 미치는 영향은 파악할 수 없게 된다.
영화 제작으로 비유하자면 기존에는 영화 배우가 대본을 짜고 상대 배우를 지정하고 촬영 세팅까지 했다면 AppConfig 등장 이후에는 영화 감독이 전체적인 촬영을 통괄하고 배우는 오로지 자신의 역할만을 연기하면 되는 것이다. 영화 배우는 대본에 따라 연기만 할 뿐 영화가 어떻게 만들어지는지 알 수 없다.
IoC의 관점에서 프레임워크와 라이브러리 차이를 명료하게 설명할 수 있다.
특정 외부 코드가 나의 코드를 제어하고 대신 실행하면 그것은 프레임워크이고, 내가 작성한 코드가 직접 특정 코드에 대한 제어의 흐름을 담당한다면 그것은 라이브러리이다.
예시로, JUnit을 이용한 테스트는 내가 만든 메소드가 JUnit의 매커니즘에 따라 수행된다. 즉 코드의 제어 흐름이 JUnit에 있고 따라서 이는 프레임워크다.
반면 queue, stack과 같은 자료구조를 import하여 목적에 맞게 내가 활용하는 경우, 해당 자료구조에 대한 제어 흐름은 나에게 있으며 따라서 이는 라이브러리이다.
정리하면, 코드의 주도권이 나에게 있으면 라이브러리, 나에게 없으면 프레임워크다.
의존 관계 주입(Dependency Injection)
: 특정 클래스나 객체가 어떤 인터페이스에 의존하는지 그 의존 관계를 주입하여 주는 것을 의미한다.
의존 관계는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계를 분리해서 생각해야 한다.
정적인 클래스 의존 관계의 경우,
클래스가 사용하는 import 코드만 보고 의존 관계를 쉽게 파악할 수 있다. 따라서 애플리케이션을 실행하지 않고도 분석할 수 있다.
예시로,
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
OrderServiceImpl은 Member나 MemberRepository를 import하여 의존한다. 이러한 정적 클래스 의존 관계만으로는 실제로 어떤 객체가 OrderServiceImpl에 주입될지 알 수 없다.
동적인 객체 인스턴스 의존 관계
애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계이다.
애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달하여 클라이언트와 서버의 실제 의존 관계가 연결되는 것을 의존 관계 주입이라고 한다. 객체 인스턴스를 생성하고 그 참조값을 전달해서 연결된다.
의존 관계 주입을 사용하면 클라이언트 코드를 변경하지 않고 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.(AppConfig 사용) 의존 관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고 동적인 객체 인스턴스 의존 관계를 쉽게 변경할 수 있다.
AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 하며, 의존 관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다. 어셈블러, 오브젝트 팩토리 등으로 불리기도 한다.
'Study > Spring' 카테고리의 다른 글
네이버 SENS API 사용 방법(feat. Feign Client) (1) | 2023.11.08 |
---|---|
연관관계 매핑 기본 개념 (0) | 2022.11.17 |
싱글톤 패턴 (0) | 2022.06.23 |
게시판에 사용한 HTML 정리(feat. thymeleaf) (0) | 2022.01.16 |
스프링 CRUD 게시판 구현(SpringBoot, MySQL, SpringDataJPA) (2) | 2021.12.29 |