DI의 정의
DI는 Dependency Injection의 줄임말로, 우리 말로는 의존관계 주입 또는 의존성 주입이라고 표현합니다.
의존이라고 하는 단어 자체가 관계 속에서 의존하는 느낌이기 때문에, 저는 의존관계 주입이라는 용어가 더 적합하다고 이해했습니다.
그렇다면, 여기서 말하는 의존관계가 무슨 말일 까요? "A가 B에 의존한다."라고 했을 때, 이것이 의미하는 바를 다음과 같은 설명을 통해 이해할 수 있었습니다.
의존대상 B가 변하면, 그것이 A에 영향을 미친다.
- 토비의 스프링 3.1
즉, B의 기능이 추가 또는 변경될 때, A에 영향을 미치는 경우 "A가 B에 의존한다."라고 표현할 수 있는 것입니다.
그렇다면, Dependency Injection은 무엇일까요?
말 그대로 의존 관계를 주입하는 것인데, 이 의존 관계를 객체(구현체)의 외부에서 결정해주는 것을 의미합니다. 그러니깐 구현체에 대한 코드를 바깥에서 넣어주도록 설정해서 현재 구현체에서는 모르게 하는(의존하지 않도록 하는) 것이라고 볼 수 있습니다.
의존 관계 주입을 하는 이유는 객체 지향 설계의 5가지 원칙 중 하나인 DIP(Dependency Inversion Principle)를 지키는 방식으로 객체 지향 설계를 하기 위함입니다.
DIP는 프로그램의 설계 시에 구현체가 아닌 추상화에 의존해야 한다는 원칙으로 그렇기 때문에, 구현체가 아닌 추상화(interface가 되겠죠)에 의존할 수 있도록 DI라는 개념을 도입한 것입니다.
토비의 스프링에서는 다음 세 가지 조건을 충족하는 작업을 의존 관계 주입이라고 정의합니다.
- 클래스 모델이나 코드에는 run-time 시점의 의존 관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
- Run-time 시점의 의존 관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
- 의존 관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.
이렇게 개념적으로만 정리하면, 이해가 잘 안 돼서 코드 예시와 함께 정리해 보도록 하겠습니다.
예시와 함께 정리해보자.
public class MemberServiceImpl implements MemberService{
// 아래의 부분이 구현체에 의존한다.
// 이러한 경우 OCP, DIP를 위배
private final MemberRepository memberRepository = new MemoryMemberRepository();
public void join(Member member){
memberRepository.save(member);
}
public Member findMember(Long memberId){
return memberRepository.findById(memberId);
}
}
예를 들어 위와 같은 식으로 코드를 작성한 경우 MemberService의 구현체(MemberServiceImpl)가 MemberRepository라는 추상(인터페이스)뿐만 아니라 MemoryMemberRepository라는 구현체에도 의존하는 문제가 발생하고 있습니다.
이건 DIP(Dependency Inversion Principle)에 위배되기 때문에, 추상에 의존하도록 변경할 필요가 있고 이를 다음과 같이 AppConfig에서 의존관계 주입을 진행하도록 코드를 변경해 DIP를 준수하도록 변경할 수 있습니다.
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
// 이제 MemberService의 구현체에 MemberRepository라는 추상화만 남게 되었다.
// 구현체에 의존하지 않아도 되도록 변경
public MemberServiceImpl(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
// 아래의 부분은 안봐도 된다.
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
이렇게 생성자를 이용해서 외부에서 의존성을 주입받도록 변경할 수 있습니다.
public class AppConfig {
// AppConfig를 사용해 의존성 주입이 가능하다.
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
private static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
AppConfig에서 애플리케이션의 실제 동작에 필요한 구현 객체를 생성하고, 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입(연결)해준다.
References
https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/
위 글에서 작성한 개념들과 코드는 김영한 님의 스프링 핵심 원리 - 기본 편 강의 내용을 바탕으로 정리했습니다.
'BackEnd > java spring' 카테고리의 다른 글
[Spring] 스프링 컨테이너와 스프링 빈 개념 정리 (0) | 2022.12.31 |
---|---|
IoC, DI 그리고 컨테이너 개념 정리 (0) | 2022.12.28 |
[Java] 자바 공부 - 4, package란? (2) | 2022.12.26 |
좋은 객체 지향 프로그래밍이란? (0) | 2022.12.24 |
좋은 객체 지향 설계의 5가지 원칙 (SOLID) (4) | 2022.12.24 |
댓글