계발자 블로그

[DI] Dependency Injection과 Dagger-Hilt 본문

Android

[DI] Dependency Injection과 Dagger-Hilt

더구더구 2024. 1. 7. 15:37

Dependency Injection (DI)

의존성 주입이라고 하며 소프트웨어 디자인 패턴 중 하나로 객체지향 프로그램의 개념 중 하나입니다.

객체 간의 의존성을 외부로부터 주입하는 방법을 제공합니다.

 

전에 작성했던 SOLID 원칙 포스트에서 의존성 역전 원칙을 코드로 실현하는 방법이자

낮은 결합도를 실현하는데 중요한 역할을 합니다.

 

[객체지향] SOLID 원칙

SOLID 원칙이란? SOLID 원칙이란 객체지향 프로그래밍(OOP)에서 지켜야 할 5개의 설계 원칙을 의미합니다. SRP (Single Responsibility Principle): 단일 책임 원칙 OCP (Open Closed Principle): 개방 폐쇄 원칙 LSP (Liskov

thuglife.tistory.com

 

의존의 영향은 꼬리의 꼬리를 문 것처럼 전파되는 특징이 있습니다.

 

위 이미지가 예시입니다.

의존관계가 있는 프로그램을 개발하다 보면 결국 자기 자신이 변경되는 즉, 의존이 순환되는 경우가 생기기도 합니다.

이것을 순환참조라고 하며 유지보수를 어렵게 만들기 때문에 반드시 피해야 될 문제입니다.

이것을 깨는 것이 의존역전 원칙이고 의존역전 원칙을 코드로 실현하는 방법이 의존성 주입입니다.

 

class DieselEngine { 
    val fuel = "diesel" 
}

class Car { 
    val engine = DieselEngine()
}

 

위 코드에서 Car 클래스는 DiselEngine 클래스를 내부에서 직접 생성했습니다.

이는 상위 레벨의 Car 클래스가 하위 레벨의 DiselEngine 클래스에 직접 의존하고 있음을 의미합니다.

즉, 의존성 역전 원칙을 위배한 것입니다.

만약 Car 클래스에서 다른 엔진을 사용하고 싶다면 Car 클래스를 직접 수정해야 합니다.

이는 확장에 대한 닫힘을 나타내는 것이고 결과적으로 확장성 폐쇄 원칙까지 위배하게 된 코드입니다.

 

 

다음은 DI의 주요 장점입니다.

 

유연성

객체 간의 의존성이 줄어들어 변경 사항이 다른 객체에 미치는 영향을 최소화합니다.

테스트 용이성

의존성을 주입받기 때문에 테스트 시에 mock objects를 사용하여 테스트를 용이하게 할 수 있습니다.

재사용성

의존성을 외부에서 주입 받기 때문에 같은 코드를 여러 곳에서 재사용할 수 있습니다.

 


Dagger-Hilt

의존성 주입을 개별적으로 구현하는 것은 번거롭고 까다롭기 때문에

안드로이드에서는 Dagger, Hilt, Koin 등의 라이브러리들이 개발되어 있습니다.

Hilt는 기존에 있던 di 방식인 dagger(단검)을 쉽게 사용하자는 뜻에서 hilt(손잡이) = Dagger-Hilt라고 탄생한 것 같습니다..ㅎㅎ

 

많은 개발자 분들이 주로 Dagger-Hilt를 사용합니다

dagger에 비해 낮은 러닝커브 koin에 비해 높은 확장성과 적은 오버헤드, 그리고 Google의 공식 지원 때문이라 생각합니다.

Hilt의 구조

 

Hilt는 어플리케이션 내부에 생명주기와 연동되는 Component라는 이름의 보관함을 만들고 그 안에 의존 객체를 생성합니다.

그리고 의존 객체들끼리 관계를 정의한 dependency graph를 만든 다음에 앱을 실행하는 중에

activity 혹은 fragment에서 의존 객체의 요청이 오면 이 graph를 참조해서 의존 객체를 Component를 통해서 반환하게 됩니다.

Component

@HiltAndoirdApp

  • Application

@HiltViewModel

  • ViewModel

@AndroidEntryPoint

  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

힐트는 현재 위와 같은 안드로이드 클래스에 대해서만 의존성을 주입할 수 있습니다.

 

@~을 붙여주면 각 클래스에 상응하는 의존정을 보관하기 위한 컴포넌트라는 박스를 작성하게 되고

이 컴포넌트 내부의 의존객체는 다른 컴포넌트를 가진 쪽에 의존성을 주입할 수 있는 상태가 됩니다.

Scope

 

힐트가 제공하는 의존 객체는 기본적으로 UnScoped 상태입니다.

앱이 객체를 요청할 때마다 새로운 객체가 만들어집니다.

이를 방지하기 위해서 각 의존 객체에는 Scope라는 이름의 생명주기를 지정할 수 있습니다.

 

이 Scope의 범위를 나타내는 것이 위 그림의 Scope Annotation입니다.

 

예를 들어 의존 객체에 @Singleton을 붙이면 앱 전체에서 하나의 객체만 만들어지게 되고

@ActivityScoped를 붙이면 해당 액티비티에서 하나의 객체만 만들어집니다.

 

그리고 이때 컴포넌트 종속 관계에 따라서 하위의 컴포넌트는 상위 스코프를 가진 의존 객체에 접근할 수 있게 됩니다.

 

주입

의존 객체를 모아둔 컴포넌트를 만들었으면 이제 이 의존 객체를 어딘가에 주입해야됩니다

컴포넌트 안에 의존 객체와 의존 객체를 주입 받을 객체를 연결하는 행위를 Binding이라고 합니다.

힐트에서는 의존성을 제공할 객체와 받을 객체 양쪽에 Inject 어노테이션을 붙임으로써 binding이 수행 됩니다.

다만 의존 객체를 주입받기 위해서는 컴포넌트가 있어야 하기 때문에 AndroidEntyPoint를 추가로 붙여줘야 합니다.

 

Module

힐트에서 의존 객체를 담는 클래스를 module이라는 이름으로 정의합니다.

컴포넌트 안에 모듈을 설치하고 그 모듈 내부의 의존 객체를 필요로 하는 곳에 주입하게 되는 것입니다.

 

정의

  • @Module
  • @Installin

모듈의 정의와 설치에는 module와 installin을 사용합니다.

 

생성

  • @Provides
  • @Binds

모듈 안에 모든 의존 객체는 다른 곳에 주입할 수 있는데

외부 라이브러로부터 만들어지는 인스턴스와 인터페이스의 인스턴스는

힐트가 생성하는게 아니기 때문에 그대로는 주입할 수 없습니다

그래서 사용하는게 provides와 binds입니다.

 

힐트를 통하지 않고 빌드 패턴이나 외부 라이브러리를 이용해서 객체를 생성해야 할 경우에는 

Provides를 이용하면 됩니다.

 

인터페이스를 의존 객체로 제공하기 위해서는 Binds를 사용하면 됩니다.

이때 주의할 점은 모듈과 함수가 모두 abstract로 선언 되야합니다.

 

 

 

 

 

참고: 인프런 냉동코더의 알기 쉬운 Modern Android Development 입문

'Android' 카테고리의 다른 글

[JetPack] Navigation (JAVA)  (0) 2022.12.04
[JetPack] WorkManager  (2) 2022.10.01
[JetPack] DataBinding  (0) 2022.08.16
[JetPack] ViewBinding  (0) 2022.07.27
[Android] Bottom Navigation View  (0) 2022.07.19