Spring/팀스파르타

15. IoC와 DI

열심히 해 2024. 10. 30. 20:40

좋은 코드란 무엇일까요?

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

 

좋은 코드를 작성하기 위해서 지켜져야 하는 여러 원칙과 원칙을 지키기 위한 방법 중 하나

 

 

 

 

 

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 와 Chicken 의 관계가 어떠한가요

  • 코드가 실행되는데 있어서는 아무런 문제가 없지만 만약 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("피자를 먹는다.");
    }
}

 

 

 


 

 

주입은 무엇일까요?

  • 우리가 주사기를 통해 백신을 우리 몸속에 '주입' 하듯이
  • 코드에서의 주입도 마찬가지로 여러 방법을 통해 필요로 하는 객체를 전달하는(받아오는) 것입니다.
  • 코드를 통해 코드에서의 주입에 대해 이해해보겠습니다.

 

 

1.필드에 주입, Consumer에 추가된 Food 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("피자를 먹는다.");
    }
}

 

 

 


 

 

IoC, 제어의 역전은 무엇일까요?

  • 이전에는 Consumer가 직접 Food(Chicken)를 만들어 먹었기 때문에 새로운 Food(Pizza)를 만들려면 추가적인 코드변경 - 피자 생성이 불가피했습니다.
    • 그렇기 때문에 이때는 제어의 흐름이 Consumer → Food(Chicken)였습니다.
    • 상위 레벨의 Consumer가 하위 레벨의 Food에 의존하는 것입니다.
  • 이를 해결하기 위해 만들어진 Food를 Consumer에게 전달해주는 식으로 변경함으로써 Consumer는 추가적인 요리준비(코드변경) 없이 어느 Food가 되었든지 전부 먹을 수 있게 되었습니다.
    • 결과적으로 제어의 흐름이 Food → Consumer 로 역전 되었습니다.
    • 하위 레벨의 Food가 상위 레벨의 Consumer에 의존하는 것입니다.
  • 전통적인 프로그래밍에서는 상위 레벨의 모듈이 하위 레벨의 모듈에 의존했습니다.
  • 하지만 이러한 방식은 코드의 재사용 및 유지 보수에 좋지 않다는 문제가 발생했습니다.
  • 따라서 의존성 주입을 통해 제어의 역전이 이뤄져야 하는 것입니다.

 

 

 

아래를 기억해주세요.

 

의존성을 주입하는 방향에 따라 제어의 흐름이 결정된다!