JAVA/JAVA | Spring 학습기록

[JAVA] IoC, DI, DIP

kth990303 2022. 8. 9. 18:01
반응형

친구와 얘기하던 중, Spring IoC, DIP 개념에 대한 얘기가 나왔다.

이 개념들은 구글링하면 워낙 잘 정리된 글들이 많아 별도로 작성하지 말까 고민도 했다. 하지만 해당 개념들은 객체지향에서 매우 중요하기 때문에 한번쯤은 포스팅하는 것이 좋을 듯하여 정리해보려 한다.


IoC(제어의 역전)

Inversion of Control, 즉 IoC는 제어의 역전이라고 번역된다.

쉽게 말하자면 어떤 객체에 대한 관리를 나 대신 다른 애한테 맡긴다는 것이다. 스프링을 쓴다면 스프링에게 특정 객체의 생성과 소멸을 빈으로 등록해 맡겨주는 것으로 생각하면 된다. 즉, 제어권을 내가 아닌 스프링 컨테이너에게 빈의 생성, 의존 관계 설정을 맡기는 것이므로 IoC가 이루어지는 것이다.

 

스프링을 사용하지 않는다고 하더라도 IoC 개념은 많이 사용된다. 특정 객체의 생성, 의존관계 설정, 삭제 등을 관리하는 책임이 위임된 객체가 있을 경우, 그 객체에게 제어권을 역전시킨거라 보면 된다.

 

IoC는 라이브러리와 프레임워크의 차이점이기도 하다.

 

This means the developer decides when to call the library. However, when we use a framework, the framework decides when to call the library. This shift in control of calling the library from the application code to the framework is an inversion of control.

출처: https://www.baeldung.com/cs/framework-vs-library

 

정말 유명한 그림이니 참고하면 좋을 듯하다.

개발자는 라이브러리를 언제 사용할지 전적으로 결정하는 제어권을 가진다. 라이브러리의 api가 반환하는 결과를 우리가 직접 이용한다. 하지만, 프레임워크는 우리에게 빠르게 개발을 할 수 있도록 골격을 제공해주고 미리 세팅을 해주어 우리가 해당 부분을 신경쓰지 않게 해준다. 우리가 중요한 것은 프레임워크가 세팅해주는 골격이 아닌, 애플리케이션 코드이다. 골격의 제어권은 프레임워크에게 넘겨주고, 우리는 골격에 맞게 애플리케이션 코드에 해당되는 코어가 되는 api를 만든다. 그리고 그 api는 프레임워크 내부에서 호출한다. 


DI(의존 관계 주입)

개발을 하다보면 웬만한 경우에서 협력 객체, 즉 의존 관계를 심심치 않게 볼 수 있다. 이러한 의존 관계를 외부에서 결정해주고 주입해주는 것을 DI, Dependency Injection이라 한다.

 

DI에는 생성자 주입 방법, Setter 주입 방법, 필드 주입 방법이 있다. 

DI는 생성자 주입 방법을 주로 권장한다. 컴파일 시점에 의존 객체가 지정됐는지 확인해줄 수 있고, 한 번 지정해주면 의존 객체를 변경할 수 없게 final 키워드로 막아줄 수 있기 때문이다. 또한, 순환 참조가 이루어지는지 스프링 애플리케이션 실행 시점에 알 수 있다.

 

그럼 왜 내부에서 의존 관계를 주입하는 것보다, 외부에서 의존 관계 주입하는 것을 허용해줄까?

바로 객체지향스럽기 때문이다.

객체지향 장점 중 하나인 변경의 유연함을 챙기기 위해서이다. 아래 글을 보면서 한번 이해해보자.

 

DI는 DIP, Dependency Inversion Principle(의존 관계 역전 원칙)와 함께 쓰였을 때 빛을 발하게 된다. SOLID 원칙 중 하나인 DIP는 고수준 모듈에 의존하라고 얘기하며, 여기서 고수준 모듈이란 상위 클래스, 즉 추상클래스나 인터페이스와 같은 한 단계 추상화된 클래스를 의미한다.

 

아래 코드를 보자.

public class LottoService {

    private final LottoRandomGenerator lottoRandomGenerator;

    public LottoService(final LottoRandomGenerator lottoRandomGenerator) {
        this.lottoRandomGenerator = lottoRandomGenerator;
    }
}

 

외부에서 의존 관계가 주입됐기 때문에 DI를 잘 지킨 것 같다. (나중에 얘기하겠지만 DIP는 지켜지지 않았다. 이 때의 문제점을 보자.)

 

로또 당첨을 결정하는 방법은 완전 랜덤이다. 그렇기 때문에 위와 같이 의존관계를 작성해주었다.

이를 그림으로 나타내면 아래와 같다.

그런데, 어느 날 로또 당첨을 결정하는 특정 메뉴얼이 생겨서 코드를 변경해야 된다고 해보자. 

그러면 위 코드는 반드시 변경돼야 한다.

왜?? LottoRandomGenerator이 아닌, LottoManualGenerator를 의존해야 하기 때문에! lottoRandomGenerator이라 돼있는 부분을 바꿔야되고, 타입 또한 바꿔주어야 한다.

즉, 추상화된 고수준 모듈이 아닌, 구현체 클래스인 저수준 모듈을 의존한 것이다. 따라서 의존 관계 역전 원칙이 지켜지지 않은 것을 확인할 수 있다.

 

만약 아래처럼 DIP가 지켜졌다면?

public class LottoService {

    private final LottoGenerator lottoGenerator;

    public LottoService(final LottoGenerator lottoGenerator) {
        this.lottoGenerator = lottoGenerator;
    }
}

위와 같이 LottoRandomGenerator, LottoManualGenerator을 한 단계 추상화한 인터페이스 (또는 추상 클래스)와 같은 고수준 클래스를 의존해 DIP를 지켰다면?

우리는 위 코드를 변경해줄 필요가 없다.

왜냐?

한단계 추상화한 LottoGenerator 클래스를 의존하기 때문에 하위 클래스의 변경이 발생해도 위 코드는 변경할 필요가 없기 때문이다. 변경에 유연하다!

DI, 즉 외부에서 의존관계를 주입해주기 때문에 외부 코드에서 LottoManualGenerator로 갈아끼워주기만 하면 된다!

 

 

추상화 및 DIP의 장점에 대해 더 자세히 알고 싶다면 예전에 작성한 객체지향과 디자인패턴 정리 포스팅을 참고하면 좋을 듯하다.

(더 좋은 건 이 책을 읽는 걸 추천)

https://kth990303.tistory.com/280

 

[호호 스터디] 다형성과 추상 타입_ 객체지향과 디자인 패턴 Chapter 3

호호 스터디에서 Chapter 3: 다형성과 추상 타입을 발표하기 전에, 미리 책을 읽고 공부한 내용을 기록한 포스팅이다. 이펙티브 자바, 오브젝트 책에 적힌 내용과 유사한 내용도 많이 있어, 함께 읽

kth990303.tistory.com

https://kth990303.tistory.com/289

 

[호호 스터디] 설계 원칙: SOLID_객체지향과 디자인 패턴 Chapter 5

호호 스터디에서 Chapter 5: 설계 원칙: SOLID를 듣기 전에, 미리 책을 읽고 공부한 내용을 기록한 포스팅이다. SOLID 원칙 어떻게 보면 귀에 딱지가 앉을 정도로 많이 들어서 너무 뻔하게 느껴질 수 있

kth990303.tistory.com

 

IoC VS DIP

IoC와 DIP를 착각하는 경우도 있다.

한 단계 추상화된 고수준 모듈에 의존하는 것은, 결국 상위 클래스에게 제어를 역전한 것이므로 IoC와 DIP는 같은 것이 아닌가? 라고 생각하는 것이다. 

 

그렇지만, 이 생각은 틀렸다고 생각한다.

'의존'과 '제어'를 혼동하는 것이라 생각한다.

제어는 그 객체의 생성, 의존관계 설정, 호출, 삭제에 대한 통제를 하는 것을 의미한다. DIP는 추상화된 클래스의 기능에 의존하는 것이지, 그 클래스를 제어하는 것에 대한 원칙이라 보기 어렵다. DIP는 의존성을 줄여, 해당 객체 코드가 변경돼도, 그와 협력하는 객체들의 코드의 변경을 최소화하는 원칙이고, IoC는 프레임워크, 또는 특정 객체에게 제어권을 위임하여 우리가 비즈니스 로직에 집중할 수 있는 원칙을 의미한다고 생각한다 :)  


친구랑 이런저런 얘기하면서 나도 OOP 개념 복습을 할 수 있었고, 간만에 재미있는 시간을 보낼 수 있었다.

개념을 다시 한 번 생각해보고, 또 토론해보는 시간을 많이 가져보자!

 

참고

 

 

 

 

반응형