스프링 부트(Spring Boot) - 의존성 주입(DI), FINAL(불변성)

2경빈·2024년 6월 18일

Spring Boot

목록 보기
4/19
post-thumbnail

의존성 주입(Dependency Injection, DI)

의존성 주입(Dependency Injection, DI)은 객체 지향 프로그래밍에서 중요한 개념으로, 객체 간의 의존 관계를 외부에서 설정하여 객체 간 결합도를 낮추고 유연한 코드를 작성할 수 있게 해준다. 이를 통해 코드의 재사용성과 테스트 용이성을 높일 수 있다.

의존성 주입의 개념과 이점

의존성(Dependency) : 객체가 다른 객체에 의존한다는 것은, 한 객체가 다른 객체의 기능을 사용하거나 데이터를 이용해야 한다는 것을 의미한다.
이는 보통 필드, 메서드 매개변수, 또는 생성자를 통해 나타낼 수 있다.

주입(Injection) : 의존성 주입은 외부에서 객체에 필요한 의존성을 제공하는 것을 말한다.
즉, 객체 스스로가 의존하는 다른 객체를 생성하거나 관리하지 않고, 외부에서 주입받아 사용하는 것이다.

결합도 감소 : 의존성 주입을 통해 객체는 의존하는 다른 객체의 구체적인 구현이 아니라 인터페이스나 추상화된 형태에만 의존하게 된다.
이로 인해 객체 간의 결합도가 낮아지며, 코드의 유연성과 재사용성이 증가한다.

테스트 용이성 : 의존성 주입을 사용하면 테스트 시에 의존하는 객체를 모의(mock) 객체로 대체하여 테스트할 수 있다.
이는 유닛 테스트와 같은 테스트 방법을 보다 쉽고 정확하게 수행할 수 있도록 도와준다.

개발자의 코드 변경 최소화 : 객체 간의 의존 관계가 외부에서 주입되기 때문에, 의존하는 객체의 변경이 필요할 때 다른 객체의 코드를 변경하지 않고 외부에서 주입받는 객체의 설정만 변경하여 처리할 수 있다.

의존성 주입 방법

의존성 주입은 객체 지향 설계 원칙(SOLID 원칙 중의 의존 역전 원칙)을 따르며, 객체 간의 결합도를 낮추고 유연하고 테스트 가능한 코드를 작성하는 데 중요한 역할을 한다.
의존성 주입은 크게 세 가지 방식으로 구현될 수 있다.

생성자 주입(Constructor Injection)

의존성을 객체를 생성할 때 생성자를 통해 주입하는 방식.
주로 필수적인 의존성을 주입할 때 사용된다.

public class BoardService {
    private final BoardDao dao;

    public BoardService(BoardDao dao) {
        this.dao = dao;
    }
}

장점

  • 객체 생성 시 의존성을 강제할 수 있어 불변성을 보장한다.
  • 의존성이 명확히 드러나므로 코드 가독성이 높아진다.

단점

  • 의존성이 많을 경우 생성자 파라미터가 많아져 코드가 복잡해질 수 있다.
  • 모든 의존성을 한 번에 주입해야 하므로 선택적 의존성 주입이 어렵다.

Setter 주입(Setter Injection)

의존성을 객체를 생성한 후 setter 메서드를 통해 주입하는 방식.
선택적인 의존성을 주입할 때 사용된다.

public class BoardService {
    private final BoardDao dao;

    public void setBoardDao(BoardDao dao) {
        this.dao = dao;
    }
}

장점

  • 선택적 의존성 주입이 가능하여 유연성이 높다.
  • 의존성 주입 후 객체를 재설정하거나 변경할 수 있다.

단점

  • 객체 생성 후 의존성을 설정하므로 불변성을 보장할 수 없다.
  • 의존성 주입이 누락될 가능성이 있어 런타임 에러가 발생할 수 있다.

필드 주입(Field Injection)

필드에 직접 주입하는 방식.
일반적으로 권장되지 않으며, 주로 DI 프레임워크에서 지원하는 경우가 있다.

public class BoardService {
    @Autowired
    private final BoardDao dao;
}

장점

  • 코드가 간결해지고 주입 코드가 최소화된다.
  • 기존 코드를 수정하지 않고 쉽게 의존성을 추가할 수 있다.

단점

  • DI 프레임워크에 강하게 결합되어 테스트가 어렵다.
  • 필드가 직접 주입되므로 의존성이 명확하지 않고, 코드 가독성이 떨어진다.

의존성 주입(Dependency Injection)이 필요한 이유

예를 들어

public class Controller{
	BoardBoard service = new BoardService();
}

이렇게 코드가 작성되어 있다면, 의존하는 BoardService가 BoardService2로 바뀌는 경우, Controller에 찾아와서 직접 바꿔주어야 한다.

이를 해결하기 위해서 BoardService, BoardService2 ~ 들이 공통적으로 상속받는 부모 클래스(인터페이스 등..)를 만들어준다.

*설명을 위해 Object라고 적혀 있었으나, 실제로는 공통적으로 상속받는 부모 클래스를 사용.

ex) 파라미터로 피카츄, 파이리, 꼬부기 등.. 뭐가 올지 모르겠다. == 포켓몬이라는 부모클래스를 만들어준다.

이러한 방법으로 유연성을 높이고, 객체간의 결합도를 낮춰줄 수 있다.

final 키워드를 사용하는 이유

private final BoardDao dao;

final 키워드를 사용하는 이유는 주로 객체의 불변성(Immutability)을 보장하고, 코드의 안정성과 가독성을 높이기 위함.
*여러 가지 이유와 장점들이 있다

  • 불변성 보장

final 필드는 한 번 초기화되면 그 값을 변경할 수 없다.
따라서 객체가 생성된 이후에는 userRepository 필드가 가리키는 객체가 변경되지 않는다.
이는 코드의 예측 가능성을 높이고, 버그 발생 가능성을 줄여준다.

  • 스레드 안전성

final 필드는 스레드 간의 동기화 문제를 방지한다.
멀티스레드 환경에서 여러 스레드가 동시에 userRepository 필드에 접근하더라도, 한 번 초기화된 후에는 안전하게 접근할 수 있다.

  • 의존성 주입(Dependency Injection)에서의 필수적 초기화

주로 Spring Framework와 같은 DI 컨테이너에서 사용될 때, final 필드는 객체 생성 시 반드시 초기화되어야 한다.
이는 DI를 통해 외부에서 객체를 주입받을 때 실수로 null 값을 가질 수 없도록 보장한다.

  • 가독성과 유지보수성

final 키워드는 코드의 의도를 명확히 전달할 수 있다.
해당 필드가 한 번 초기화되면 변경되지 않음을 명시적으로 표현하며, 이는 코드를 이해하기 쉽고 유지보수하기 쉽게 만든다.

  • 컴파일러 최적화

final 필드는 컴파일러가 성능 최적화를 할 수 있는 여지를 준다.
컴파일 시간 상수(fold-time constants)로 처리할 수 있어 일부 성능상의 이점을 제공할 수도 있다.

따라서 'private final BoardDao dao;'와 같이 필드를 선언함으로써 코드의 안정성과 가독성을 높이며, 객체의 불변성을 보장할 수 있다.
이는 객체 지향 프로그래밍의 기본 원칙 중 하나인 "객체의 상태는 외부에서 변경되지 않아야 한다"는 원칙을 준수하는 방법 중 하나이다.

profile
eggs before hatching

0개의 댓글