본격적으로 스프링을 공부해보기 앞서 스프링에서 중요하게 여겨지는 개념인
DI(의존성 주입) , IoC(제어의 역전)에 대해서 알아보자
모듈 : 하나 또는 몇개의 논리적 기능을 수행하기 위한 명령어의 집합
독립 프로그램도 하나의 모듈이 될 수 있고 하나의 객체도 모듈이 될 수 있다.
모듈간의 결합은 느슨하게
모듈 내 요소의 응집은 강하게
DI(의존성 주입)을 알아보기 전에 DIP에 대해서 먼저 알아볼 필요가 있다
Step 01 객체 지향 프로그래밍에서 객체지향 설계 원칙 S.O.L.I.D를 언급한 적이 있다.
S.O.L.I.D의 D가 DIP를 의미하는 것이다.
의존 관계 역전 원칙 : 클라이언트는 구체 클래스가 아닌 추상 클래스(인터페이스)에 의존해야 한다.
즉, 고수준 모듈이 저수준 모듈에 의존하면 안된다.
객체 지향 설계에서 의존 관계는 결코 반가운 단어가 아니다.
서로의 관계가 의존적이라는 것은 결합도가 높다는것을 의미하고
이는 객체 지향적 설계에서 좋은 관계가 아니다.
class ShoppingCart {
private ProductDatabase productDatabase;
public ShoppingCart() {
productDatabase = new ProductDatabase();
}
// ...
}
class ProductDatabase {
public Product getProductById(int id) {
// ...
}
}
위의 코드에서 'ShoppingCart'클래스는 'ProductDatabase'클래스에 직접 의존하고 있다.
이는 고수준 모듈(ShoppingCart)가 저수준 모듈(ProductDatabase)에 의존하는 것이므로 DIP를 위반한 것이다.
interface ProductProvider {
Product getProductById(int id);
}
class ProductDatabase implements ProductProvider {
public Product getProductById(int id) {
// ...
}
}
class ShoppingCart {
private ProductProvider productProvider;
public ShoppingCart(ProductProvider productProvider) {
this.productProvider = productProvider;
}
// ...
}
'ProductProvider'라는 인터페이스를 도입하여 'ProductDatabase'클래스가 이 인터페이스를 구현하도록 작성하였고, 'ShoppingCart'클래스가 'ProductProvider'인터페이스에 의존하도록 변경했다.
'ShoppingCart'클래스는 'ProductDatabase'클래스에 직접 의존하는것이 아닌, 인터페이스에 의존하게 된다.
이렇게 되면 'ShoppingCart'클래스를 확장하거나 인터페이스를 사용하여 쉽게 변경할 수 있다.
예를 들어, 'ProductDatabase'를 변경하고자 한다면 인터페이스를 구현하는 다른 클래스를 만들기만 하면 된다.
DI를 통해 DIP를 만족하는 코드를 작성할 수 있다.
의존성 주입은 클래스간의 의존성을 외부에서 주입받도록 하는것을 의미한다.
위의 예시를 다시 가져와 보자
interface ProductProvider {
Product getProductById(int id);
}
class ProductDatabase implements ProductProvider {
public Product getProductById(int id) {
// ...
}
}
class ShoppingCart {
private ProductProvider productProvider;
// 생성자를 통해 주입했으므로 객체 생성과 동시에 주입됨
public ShoppingCart(ProductProvider productProvider) {
this.productProvider = productProvider;
}
// ...
}
객체를 생성하는 'new' 키워드는 눈을 씻고 찾아봐도 보이지 않지만 'ShoppingCart'는 분명히 'ProductProvider'객체를 사용하고 있다.
이게 어떻게 가능할까?
위 코드는 생성자 주입 방식으로 DI가 구현되어 있기 때문이다.
'ShoppingCart'클래스는 생성자를 통해 'ProductProvider'인터페이스를 받는다.
이렇게 하면 'ShoppingCart'클래스는 'ProductProvider'의 구체적인 구현에 의존하지 않고
인터페이스 자체에만 의존하게 된다.
이외에도 메서드 주입 방식(보통 setter), 필드 주입 방식이 있지만
스프링에서 권장되는 방식은 생성자 주입 방식이다.
Ioc는 프로그램의 제어 흐름(라이프 사이클이라고 해도 될까?)을 개발자가 직접 제어하는것이 아닌 외부 컨테이너(ex. 스프링 프레임워크)가 제어의 흐름을 가저가는 것이다.
DI는 IoC의 구체적인 구현 방법 중 하나이기 때문에 위의 예시에도 역시 IoC가 구현되어 있다.
위의 예시에서 'ShopingCart'클래스는 'ProductProvider'를 생성하는것이 아니라
외부에서 생성된 'ProductProvider'를 주입받게 된다.
이렇게 함으로써 'ShopingCart' 클래스는 'ProductProvider'의 생성 및 관리를 개발자가 아닌
외부 컨테이너에게 위임하고 , 제어의 흐름이 개발자가 아닌 외부에 의해 결정되게 된다.
이것이 IoC의 핵심이다.
DI와 IoC는 이전에 스프링을 공부(를 시도) 해보면서 들어 본 적이 있다.
정말 들어만 봤다.
정확히 무엇일까 생각해보니 정말 하나도 생각이 나지 않았다.
그래서 오늘 정확히 이해하고 싶어서 많은 레퍼런스들을 찾아보며 공부했다.
하지만 아직도 개념이 붕 뜬 느낌이다.
때문에 내가 작성한 글도 정확한 개념이 아닐 수 있다.(호오옥시 내가 쓴 글을 참고한다면 이점도 유의해 주길 바람)
앞으로 꾸준이 스프링을 사용하며 쓰게 될 개념이니 직접 작성해보며 손과 눈과 뇌가 기억하도록 공부해야겠다.