0+ 스프링/0+ 스프링 Core

[스프링 Core] 도대체 DI(Dependency Injection) 란 무엇인가?

힘들면힘을내는쿼카 2023. 6. 17. 18:09
728x90
반응형

[스프링 Core] 도대체 DI(Dependency Injection) 란 무엇인가?

 

스프링의 핵심 3대 요소 AOP, IOC, DI를 들어보신 적이 있을 것 입니다.


오늘은 DI가 도대체 무엇인지 확실하게 설명해드리겠습니다…!!


그리고 스프링 컨테이너가 IoC인 이유를 알아봅시다.

 

문제 인식 🤔

public class Driver {
    private Avante avante;

    public Driver() {
        this.avante = new Avante();
    }

    public void drive() {
        avante.start();
    }
}

 

위 클래스는 2가지 문제점이 있습니다.

  • 두 클래스가 강하게 결합
  • 객체들간의 관계가 아니라 클래스간의 관계가 맺어짐

 

두 클래스가 강하게 결합

Driver 클래스는 Avante 클래스와 강하게 결합되어 있는 문제점이 있습니다.
이말은 만약에 Avante가 아닌 Model3를 소유하게 된다면 Driver클래스의 생성자 변경이 필요합니다.

 

클래스 간의 관계가 맺어짐

올바른 객체지향적 설계라면 객체들 간에 관계가 맺어져야 합니다.

객체들간에 관계가 맺어졌다면, DriverAvante인지 Model3인지
신경쓰지 않고, drive()를 호출 할 수 있어야 합니다.

 

DI는 디자인 패턴이다. 🎨

이러한 문제를 해결하기 위해 DI가 등장했습니다.
DI를 사용하기 위해서는 다형성이 필요합니다.


Avante, Model3를 하나로 표현하는 추상화 작업(인터페이스)이 필요합니다.

 

DI외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴 입니다.

두 객체 사이에 인터페이스를 두어서 클래스 레벨에서는 의존관계가 고정되지 않도록 합니다.
두 객체 간에 🏃‍♂️ 런타임 시에 관계를 동적으로 주입하여 느슨한 결합을 할 수 있습니다.

 

이게 무슨말인지 이해가 안될 수 있기 때문에 코드로 보여드리겠습니다.

 

Car

public interface Car {
    void start();
}

 

Avante

public class Avante implements Car {
    @Override
    public void start() {
        System.out.println("아반떼에 시동을 걸었습니다.");
    }
}

 

Model3

public class Model3 implements Car {
    @Override
    public void start() {
        System.out.println("모델3에 시동을 걸었습니다.");
    }
}

 

이런식으로 설계하면,
우리는 이제 Driver 클래스에서 강하게 결합되어 있는 AvanteCar로 대체(추상화)할 수 있습니다.

강한 결합을 제거하기 위해 외부에서 Car를 주입받습니다.

 

Driver

public class Driver {
    private Car car;

    public Driver(Car car) {
        this.car = car;
    }

    public void drive() {
        car.start();
    }
}

 

이것이 바로 DI 입니다.

 

Dirver Car라는 인터페이스를 의존하기 때문에
Car의 구현체인 Avante, Model3 중 무엇이 사용되는지 신경쓰지 않고 역할을 다할 수 있습니다.

 

스프링에서 DI

이러한 역할을 스프링 프레임워크가 완벽하게 지원을 해줍니다.

스프링 애플리케이션을 실행하는 시점에 빈(Bean)으로 등록된 객체를 생성하고, 의존성에 맞게 알아서 주입해줍니다.

public class BeanFactory {
    public void driver() {
        // Bean 생성
        Avante avante = new Avante();
        // 의존성 주입
        Driver driver = new Driver(avante);
    }
}

 

스프링은 특정 위치로 부터 클래스를 탐색하고, 객체를 생성하여 객체 관계까지 설정해 줍니다.
이러한 이유로 스프링은 DI 컨테이너라고 불립니다.

 

그리고 이러한 개념은 더 나아가 IoC(Inversion of Control)라고 불립니다.
어떠한 객체를 사용할지에 대한 책임은 스프링에게 넘어갔고, 개발자는 수동적으로 주입받는 객체를 사용하기 때문입니다.!!! 👍

 

DI를 사용하면
자연스럽게 DIP(의존성 역전 원칙)을 지킬 수 있습니다.

DIP?
객체에서 어떤 Class 참조할 때
Class를 직접 참조하지 말고
Class의 상위 요소(추상 클래스 or 인터페이스)를 참조하라는 원칙

 

정리

운전면허가 있는 여러분은 자동차 모델에 신경쓰지 않고 운전을 할 수 있습니다.
자동차 모델에 따라서 운전방법이 달라지면 안됩니다.
그렇게 되면 자동차 모델에 따라서 운전하는 모든 방법을 알아야 합니다.

이처럼 Driver 클래스가 특정한 구현 클래스(Avante, Model3)에 의존한다고 해서 역할이 변경되면 안됩니다.

 

객체지향의 다형성을 활용하여
역할(인터페이스)과 구현(인터페이스를 구현)을 명확하게 분리 하자!!

 

DI의 이점

  • 두 객체 간의 결합도를 낮춘다.
  • 객체의 유연성이 올라간다.
  • 테스트 작성이 용이함.
    • 자동체 모델에 신경쓰지 않고 drive() 할 수 있기 때문
  • DIP를 준수하게 된다.

 

 

참고

 

 

 

728x90
반응형