IoC와 DI에 대해 항상 헷갈렸던 것 같아
정확한 개념을 정리해보려 한다.
IoC는 코드 간 결합도를 낮추고 유지보수성을 증가시키기 위한 원칙(Principle)이고 DI는 이를 구현하려는 디자인 패턴이다.
우리 말로 번역하면 제어의 역전 정도가 되겠다.
Don't call us, we will call you.
- 헐리우드 원칙(Hollywood Principle)
이러한 원칙을 프로그래밍에 적용하게 되면,
저수준 구성요소는 고수준 구성요소를 호출할 수 없게 되고
고수준 구성요소가 필요할 때 저수준을 호출하게 된다.
IoC는 메소드나 객체의 호출을 개발자가 결정하는 것이 아니라 외부에서 결정하자는 원칙이다
특히 스프링 프레임워크가 이를 관리하며 의존성 객체를 생성해
필요한 곳에 주입시켜주는 방식을 취한다.
💡 프레임워크와 라이브러리의 차이점
프레임워크는 라이브러리의 메소드를 적절한 시점에서 호출해 사용하도록 돕는다.
즉 라이브러리는 단순 활용가능한 도구들의 집합이고,
이에 대한 제어를 외부로 돌리는 IoC가 적용된 것이 프레임워크이다.
의존성 주입은 IoC를 실현하기 위한 디자인 패턴이다.
IoC의 하위 개념으로 생각할 수 있겠다.
DI를 적용하므로써, 객체 간 결합도를 줄이고 확장성을 높힐 수 있다.
대표적으로 세 가지 방식으로 DI를 구현한다.
// 1. setter를 통한 주입
@Service
public class TestService {
private final List<Test> testList;
@Autowired
public void setTests(List<Test> tests) {
this.testList = tests;
}
...
}
// 2. 생성자를 통한 주입
@Service
public class TestService {
private final List<Test> testList;
@Autowired
public TestService(List<Test> testList) {
this.testList = testList;
}
...
}
// 3. 필드 주입
@Service
@RequiredArgsConstructor
public class TestService {
@Autowired
private final List<Test> testList;
...
}
이 중 가장 권장되는 방식은 생성자를 통한 주입이다.
먼저 필드 주입은 IDE에서도 WARNING을 발생시킨다.
외부에서 변경이 불가능해 테스트하기 어렵다는 단점이 있기 때문이다.
setter를 통한 방식도 잘 사용되지 않는다.
의존관계를 변경될 수 있기 때문이다.
대부분의 의존관계는 애플리케이션 종료 전까지 변할 일이 없으며,
setter를 통한 주입을 하려면 public 메서드로 선언해야하는데,
변경하면 안되는 것을 접근할 수 있게 두는 것은 좋은 설계가 아니다.
그래서 생성자 주입을 사용한다.
객체를 생성할 때 딱 1번만 호출되고 이후에 호출되는 일이 없으므로 불변하게 설계할 수 있으며, 생성자에 원하는 객체를 넘겨 의존관계를 객체를 생성하며 다양하게 설정할 수 있다.