LUMI_dev

IoC(제어의 역전)와 DI(의존성 주입) 본문

스파르타 코딩 클럽 | 자바 심화 과정/Spring Master (입문 주차)

IoC(제어의 역전)와 DI(의존성 주입)

luminous_dev 2025. 1. 26. 17:50

IoC(제어의 역전)와 DI(의존성 주입)은 SOLID 원칙 그리고 GoF의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴

 

Ioc = 설계 원칙
DI = 디자인 패턴 

 

0. 설계 원칙? 디자인 패턴?

더보기

 

설계 원칙 디자인 패턴
김치 볶음밥 맛있게 만드는 방법 김치 볶음밥 레시피
🧑‍🍳 맛있는 김치 볶음밥을 만들기 위한 원칙
  • 신선한 재료를 사용한다.
  • 신 김치를 사용한다.
  • 밥과 김치의 비율을 잘 맞춰야 한다.
  • 볶을 때 재료의 순서가 중요하다.
🍳 맛있는 김치 볶음밥을 만들기 위한 황금 레시피
  1. 오일을 두른 팬에 채썬 파를 볶아 파기름을 만든다.
  2. 준비한 햄을 넣고 볶다가, 간장 한스푼을 넣어 풍미를 낸다.
  3. 설탕에 버무린 김치를 넣고 함께 볶는다.
  4. 미리 식혀 둔 밥을 넣어 함께 볶는다.
  5. 참기름 한스푼을 넣어 마무리한다. 

 

Spring Docs 

DI 패턴을 사용하여 IoC 설계 원칙을 구현하고 있다.

 

 

 

1. DI 를 이해하려면 '의존성'을 알아야 한다.

결합도가 높은 코드 

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가 치킨이 아니라 피자를 먹고 싶어한다면? → 많은 코드의 변경이 불가피함
  • 해결방법 : Java의 Interface를 활용하여 해결할 수 있음 

Interface 다형성의 원리를 사용하여 약한 결합 및 약한 의존성으로 수정된 코드 

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("피자를 먹는다.");
    }
}

 


2. 주입이란?

    : 여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것 

     : 외부에서 미리 만든 객체를 주입하는 것이 DI 패턴

     방법 1) 필드에 직접 주입

     : Food를 Consumer에 포함시키고 Food에 필요한 객체를 주입 받아 사용하는 방법 

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("피자를 먹는다.");
    }
}

 

  방법 2) 메서드를 통한 주입

   : set 메서드를 사용하는 방법 

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("피자를 먹는다.");
    }
}

 

  방법 3) 생성자를 통한 주입

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("피자를 먹는다.");
    }
}

 


3. 제어의 역전이란?(IoC)

수정 전 코드 : Consumer이 직접 Food를 만들어 먹었기 때문에 추가적인 요리 준비(코드 변경) 불가

                       (제어의 흐름) Consumer → Food

수정 후 코드 : Food를 Consumer에게 전달해주는 방식 

                       (제어의 흐름) Food → Consumer (역전)