IoC와 DI

이유진·2024년 8월 22일
0
post-thumbnail

🌷IoC와 DI

Bean을 공부하기전에 나오는 얘기가 있었다. 바로 IoC와 DI이다. 그런데 대체 저게 뭐지...?😐

스프링을 공부하다 보면 저 개념이 계속 나와서 한번 정리하고 이해하는 시간이 필요해 정리해 보았다!!

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

  • 객체지향의 SOLID 원칙 그리고 GoF 의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴

  • IoC설계 원칙에 해당하고 DI디자인 패턴

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

    • 🧑‍🍳 맛있는 김치 볶음밥을 만들기 위한 원칙

      • 신선한 재료를 사용한다.
      • 신 김치를 사용한다.
      • 밥과 김치의 비율을 잘 맞춰야 한다.
      • 볶을 때 재료의 순서가 중요하다.
  • 김치 볶음밥 레시피 (디자인 패턴)

    • 🍳 맛있는 김치 볶음밥을 만들기 위한 황금 레시피

      1. 오일을 두른 팬에 채썬 파를 볶아 파기름을 만든다.
      2. 준비한 햄을 넣고 볶다가, 간장 한스푼을 넣어 풍미를 낸다.
      3. 설탕에 버무린 김치를 넣고 함께 볶는다.
      4. 미리 식혀 둔 밥을 넣어 함께 볶는다.
      5. 참기름 한스푼을 넣어 마무리한다.
  • DI 패턴을 사용하여 IoC 설계 원칙을 구현하고 있다’


의존성이란?

강하게 결합

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 가 치킨이 아니라 피자를 먹고 싶어 한다면? 많은 수의 코드 변경이 불가피!


약한 결합(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("피자를 먹는다.");
    }
}
  • Interface 다형성의 원리를 사용하여 구현하면 고객이 어떠한 음식을 요구하더라도 쉽게 대처할 수 있다.

  • 이러한 관계를 약한 결합약한 의존성 이라고 할 수 있다.


주입

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

  • 필드에 직접 주입

    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("피자를 먹는다.");
        }
    }
  • Setter 메서드를 통한 주입

    public class Consumer {
    
        Food food;
    
        void eat() {
            this.food.eat();
        }
    
        // set 메서드 사용하여 필요한 객체 주입받아 사용
        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("피자를 먹는다.");
        }
    }
  • 생성자를 통한 주입(가장 권장)

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

제어의 역전

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

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

    • 결과적으로 제어의 흐름이 Food → Consumer 로 역전 되었다.
  • 제어의 역전

    • 제어의 역전은 다른 객체를 직접 생성하거나 제어하는 것이 아니라 외부에서 관리하는 객체를 가져와 사용하는 것 == 2번!!

      1.클래스 A에서 클래스 B객체 생성
      public class A {
          b = new B();
      }
      2.스프링 컨테이너가 객체를 관리하는 방식
      public class A {
          private B b;
      }
    • 클래스 B 객체를 직접 생성하는 것이 아니므로, 어딘가에서 받아와서 사용하고 있다고 추측

    • 제어의 역전을 적용하면 객체를 외부에서 관리하게 되고, 실제로 사용할 때에는 외부에서 제공해주는 객체를 받아오게 된다.

      • 외부(=객체를 관리하고 관리하는 주체)를 “스프링 컨테이너
  • IoC 개념 도입의 장점

    • 프로그램의 진행 흐름과 구체적인 구현을 분리시킬 수 있다.
    • 개발자는 비즈니스 로직에 집중할 수 있다.
    • 구현체 사이의 변경이 용이하다.
    • 객체 간 의존성이 낮아진다.

참고 URL

profile
🙌중요한건 꺾였는데도 그냥 하는 마음

0개의 댓글