[Spring] IoC와 DI

최혜원·2023년 10월 6일
0

Spring

목록 보기
4/19
post-thumbnail

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

IoC, DI는 객체지향의 SOLID 원칙 그리고 GoF 의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴입니다.
이 둘을 더 자세하게 구분해 보자면 IoC는 설계 원칙에 해당하고 DI는 디자인 패턴에 해당합니다.

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

⭐️의존성 주입

어떤 객체에 스프링 컨테이너가 또 다른 객체와 의존성을 맺어주는 행위

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

public class Consumer {

    void eat() {
				// Consumer 가 직접 음식을 만들고 있음
				// 제어의 흐름 Consumer → Food
        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("치킨을 먹는다.");
    }
}
  • 이처럼 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("피자를 먹는다.");
    }
}
  • 처럼 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;

		// Consumer 생성자(오버로딩)
    public Consumer(Food food) {
        this.food = food;
    }

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

    public static void main(String[] args) {
				// Consumer class를 생성할 때 생성자를 통해 객체가 생성됨
				// (Food의 구현체를 넣어줌)
				// Consumer는 가만히 있고 뭐 먹고싶은지 이야기만 함!
				// 제어의 흐름 Food → Consumer 
				// 외부(main)에서 직접 요청하는 음식을 만들어서 집어 넣는 것
				// 음식을 먼저 만들고 그 만들어진 음식을 Consumer에게 집어 넣고 있다.
        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 로 역전 되었습니다.
profile
어제보다 나은 오늘

0개의 댓글