스프링 - IoC와 DI의 개념

2024. 8. 2. 15:48스프링

2024/08/02

 

 

스프링을 처음 배울 때 접하는 대표적인 용어에는 IoC와 DI가 있습니다.
스프링에서 가장 중요한 개념이라 할 수 있죠.
사실 용어를 들었을 때 이 단어들이 과연 무엇을 뜻하는지 별로 와닿지 않습니다.
그래서 대충 이해하고 넘어가기도
오늘은 이 둘의 개념과 차이점을 한번 알아보겠습니다.

 


 

※ 스프링에서 가장 중요한 개념인 IoC와 DI에 대해 알아보자.

 

 

   ▶  IoC(제어의 역전)와 DI(의존성 주입) 이해하기  

     -  Spring으로 개발을 입문한 분들이 처음에 가장 많이 오해하는 것이 바로 IoC와 DI가 Spring에서 처음으로 만든 기능이라고 생각한다는 것
     -  IoC, DI는 객체지향의 SOLID 원칙 그리고 GoF의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴을 말한다.
     -  이 둘을 더 자세하게 구분해 보자면 IoC설계 원칙에 해당하고 DI디자인 패턴에 해당한다.
         
         ❓설계 원칙과 디자인 패턴 이 둘은 어떻게 다른데? 
                 EX) 요리에 빗대어 이해해보자.

        김치볶음밥 만드는 방법(설계 원칙)

🧑‍🍳  맛있는 김치 볶음밥을 만들기 위한 원칙
      ●   신선한 재료를 사용한다.  
      ●   신 김치를 사용한다.
      ●   밥과 김치의 비율을 잘 맞춰야 한다.
      ●   볶을 때 재료의 순서가 중요하다.

   
      김치볶음밥 레시피(디자인 패턴)

🍳  맛있는 김치 볶음밥을 만들기 위한 황금 레시피    
     ●   오일을 두른 팬에 채썬 파를 볶아 파기름을 만든다.
     ●   준비한 햄을 넣고 볶다가, 간장 한스푼을 넣어 풍미를 낸다.
     ●   설탕에 버무린 김치를 넣고 함께 볶는다.
     ●   미리 식혀 둔 밥을 넣어 함께 볶는다.
     ●   참기름 한스푼을 넣어 마무리한다. 

   

     -   IoC와 DI는 좋은 코드 작성을 위한 Spring의 핵심 기술 중 하나

      ❗좋은 코드란?

🧑‍💻  좋은 코드란 무엇일까요?
    ●   논리가 간단해야 한다.
    ●   중복을 제거하고 표현을 명확하게 한다.
    ●   코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
    ●   의존성을 최소화해야 한다.
    ●   새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.
    ●   … 

 

 

[그림1] Spring Docs에서 발췌한 내용

 

  • Spring 의 핵심 기술을 소개하는 Docs에서 가장 처음으로 IoC 컨테이너에 대해서 설명하고 있다.
  •  IoC에 대해 ‘IoC는 DI로도 알려져 있다.’ 라고 소개하고 있다.
  • 의역해보자면 ‘DI 패턴을 사용하여 IoC 설계 원칙을 구현하고 있다.’ 라고 이해하면 된다. 

 

   ▶  의존성이란? 

    ☞   DI가 무엇인지 알기 앞서, '의존성'에 대한 이해가 필요하다.

             "A가 B를 의존한다"는 것은 의존 대상 B가 변하면 그것이 A에 영향을 미친다는 것을 의미한다.
즉, B의 기능이 추가 또는 변경되거나 형식이 바뀌게 되면 그 영향이 A에 미치게 된다.

 

다음 코드를 통해 의존성에 대해 간략히 알아보자.

강하게 결합되어 있는 Consumer와 Chicken

public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat();
    }
}

class Chicken {
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

       ●  위 코드에서 Consumer와 Chicken은 강하게 결합되어 있다. 
       ●  그 말은 즉슨, 만약 Consumer가 Chicken이 아닌 Pizza를 먹고 싶어 한다면 수정해야할 코드가 많다는 뜻이다.  

약하게 결합되어 있는 Consumer와 Chicken

public class Consumer {

    void eat(Food food) {
        food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat(new Chicken());
        consumer.eat(new Pizza());
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

       ●  Interface 다형성의 원리를 사용해 다시 구현해보았다. 이제 고객이 어떠한 음식을 요구하더라도 대량의 코드 수정 없이 쉽게 대처가 가능하다.  
       ●  이러한 관계를 약한 결합 및 약한 의존성이라 할 수 있다. 

 

   ▶  주입이란? 

    ☞   DI가 무엇인지 알기 앞서, '주입'에 대한 이해가 필요하다.
        -  코드에서 주입이란 여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것을 말한다.  
     

다음 코드를 통해 주입을 이해해보자.

    ①  필드에 직접 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.food = new Chicken();
        consumer.eat();

        consumer.food = new Pizza();
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

        -  이처럼 Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용할 수 있다.

   ②  메서드를 통한 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.setFood(new Chicken());
        consumer.eat();

        consumer.setFood(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

        -  이처럼 set 메서드를 사용하여 필요한 객체를 주입받아 사용할 수 있다.

   ③  생성자를 통한 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();

        consumer = new Consumer(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

        -  이처럼 생성자를 사용하여 필요한 객체를 주입받아 사용할 수 있다.


정리하자면,
   ▶  DI란 무엇인가?

     -   Dependency Injection의 약자로 의존성 주입이라 한다. 

     "스프링 컨테이너에서 객체 Bean을 먼저 생성해두고 생성한 객체를 지정한 객체에 주입하는 방식을 의존성 주입이다."

      -   객체 자체가 코드 상에서 객체 생성에 관여하지 않아도 되기때문에 객체 사이의 의존도를 낮출 수 있다.
      -   스프링 컨테이너에서 객체의 생명주기를 관리하며 객체의 의존관계 또한 관리해준다.
      -   의존성 주입을 사용하면 유연하고 확장성이 뛰어난 코드 작성이 가능해진다.

 

 

   ▶  IoC란 무엇인가?

     -   Inversion of Control의 약자로 제어의 역전이라 한다.
     -   말 그대로 프로그램의 제어 흐름이 뒤바뀐 것을 말한다.    
     -   강한 결합 상태의 MVC패턴 프로그램은 제어의 흐름이 Controller ⇒ Service ⇒ Repository 순으로 비효율적인 코드로 구성되어 있다.
     -   하지만 DI 즉, 의존성 주입을 통해 제어의 흐름은 Repository ⇒ Service ⇒ Controller 로 역전함으로써 효율적인 코드로 바뀐다.

 

[그림2] 왼쪽과 오른쪽의 코드 흐름을 비교해본다.

 

💡  위에서 의존성을 설명할 때 든 예시로 제어의 역전을 좀 더 이해해보자.


     이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에 새로운 Food를 만들려면 추가적인 요리준비(코드변경)가 불가피했다.
      ∴   그렇기 때문에 이때는 제어의 흐름Consumer Food 가 된다.

      이를 해결하기 위해 만들어진 Food를 Consumer에게 전달해주는 식으로 변경함으로써 Consumer는 추가적인 요리준비(코드변경) 없이 어느 Food가 되었든지 전부 먹을 수 있게 되었다.
      ∴   결과적으로 제어의 흐름FoodConsumer 로 역전되었다.

 

 

 

IoC와 DI에 대해 이해하셨나요?
그렇다면 다음 시간에는 IoC 컨테이너와 Bean에 대해 알아보겠습니다.

 


※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.

'스프링' 카테고리의 다른 글

스프링 - 영속성 컨텍스트란?  (0) 2024.08.13
스프링 - Entity란?  (0) 2024.08.12
스프링 - Spring Data JPA란 무엇일까?  (0) 2024.08.09
스프링 - IoC Container와 Bean  (0) 2024.08.08
쿠키와 세션이란 무엇일까?  (0) 2024.08.05