✏️ [Spring] DI(Dependency Injection)

박상민·2024년 5월 26일

Spring

목록 보기
6/12
post-thumbnail

⭐️ DI란?

  • DI, Dependency Injection: 의존관계 주입이라는 뜻, IOC를 실제로 구현하는 방법.
  • 객체 사이의 의존관계를 코드로 명시하는 것이 아니라 스프링 설정 파일에 등록된 정보를 바탕으로 컨테이너가 자동으로 처리 해준다.
  • 컨테이너가 직접 객체들 사이에 의존관계를 처리하는 것

의존성이 삽입된다는 의미로 IoC를 DI라는 표현으로 사용한다.

Dependency(의존성)

  • 객체지향언어에서 두 클래스 간의 협력하는 관계
  • 일반적으로 둘 중 하나가 다른 하나를 어떤 용도를 위해 사용

📌 Dependency는 위헙하다.

하나의 모듈(클래스)이 바뀌면 다른 모듈까지 변경이 이루어지기 때문에 Dependency는 위험하다. 테스트 가능한 어플을 만들때 의존성이 있으면 유닛테스트 작성이 어렵다.

  • 유닛테스트의 목적 자체가다른 모듈로부터 독립적으로 테스트하는 것을 요구하기 때문이다. (Mock 객체로 대체 가능하다)
  • Controller는 Repository에 의존한다 (IoC 구현전)

📌 Dependency 의존관계란?

"A가 B를 의존한다."는 말은 의존 대상 B가 변하면 그것이 A에 영향을 미친다는 것이다.

바리스타를 예시를 들어보자.

바리스타는 커피 레시피에 의존한다. 커피 레시피가 변화하게 되었을 때, 변화된 레시피에 따라서 바리스타는 커피 만드는 방법을 수정해야 한다.
레시피의 변화가 바리스타의 행위에 영향을 미쳤기 때문에 바리스타는 레시피에 의존한다고 할 수 있다.

class Barista {
    private CoffeRecipe coffeRecipe;

    public Barista() {
        CoffeRecipe coffeRecipe = new CoffeRecipe();        
    }
}

의존관계를 인터페이스로 추상화
Barista 예시를 보면 CoffeRecipe만을 의존할 수 있는 구조로 되어있다.
다양한 CoffeRecipe를 의존받을 수 있게 구현하려면 인터페이스로 추상화해야 한다.

class Barista {
    private CoffeRecipe coffeRecipe;

    public Barista() {
        CoffeRecipe coffeRecipe = new CoffeRecipe();
    }
}

interface CoffeRecipe {
    newCoffe();
    // 이외의 다양한 메소드
} 

class CoffeRecipe implements CoffeRecipe {
    public Coffe newCoffe() {
        return new Coffe();
    }
    // ...
}

class MilkteaRecipe implements CoffeRecipe {
    public Coffe newCoffe() {
        return new Coffe();
    }
    // ...
}

의존관계가 무엇인지, 다양한 의존관계를 위해 인터페이스로 추상화를 알아봤다.
그렇다면 DI(Dependency Injection)은 무엇일까?

지금까지의 구현에서는 Barista 내부적으로 의존 관계인 CoffeRecipe가 어떤 값을 가질지 직접 정하고 있었다. 만약 어떤 CoffeRecipe를 만들지를 손님이 결정하는 상황을 생각해보자.
즉, 바리스타가 의존하고 있는 커피 레시피를 외부(손님)에서 결정하고 주입하는 것이다.
이처럼 그 의존관계를 외부에서 결정하고 주입하는 것이 의존관계 주입이다.

📌 DI 구현 방법

DI는 의존관계를 외부에서 결정하는 것이기 때문에, 클래스 변수를 결정하는 방법들이 곧 DI를 구현하는 방법이다.
런타임 시점의 의존관계를 외부에서 주입하여 DI 구현이 완성된다.

생성자 주입

class Barista {
    private CoffeRecipe coffeRecipe;

    public Barista(CoffeRecipe coffeRecipe) {
        this.coffeRecipe = coffeRecipe;
    }
}

class Customer {
    private Barista barista = new Barista(new CoffeRecipe());

    public void changeMenu() {
        Barista = new Barista(new HotCoffeRecipe());
    }
}

Setter 주입

class Barista {
    private CoffeRecipe coffeRecipe;

    public void setCoffeRecipe(CoffeRecipe coffeRecipe) {
        this.coffeRecipe = coffeRecipe;
    }
}

class Customer {
    private Barista barista = new Barista();

    public void changeMenu() {
        Barista = setCoffeRecipe(new HotCoffeRecipe());
    }
}

📌 DI 장점

  • 의존성이 줄어든다.
    • 의존한다는 것은 그 의존 대상의 변환에 취약하는 것이다. DI로 구현하게 되었을 때, 주입받는 대상이 변하더라도 그 구현 자체를 수정할 일이 없거나 줄어들게 된다.
  • 재사용성이 높은 코드가 된다.
    • 기존에 Barista 내부에서만 사용되었던 CoffeRecipe를 별도로 구분하여 구현하면 다른 클래스에서 재사용할 수가 있다.
  • 테스트하기 좋은 코드가 된다.
    • 테스트를 분리하여 진행할 수 있다.
  • 가독성이 높아지다.
    • 기능들을 별도로 분리하게 되어 가독성이 높아진다.

출처
https://github.com/devham76/tech-interview-study/blob/master/contents/spring.md

0개의 댓글