-
(DI - 1편) 의존성 주입이란안드로이드 학습/Android 기술면접 대비 2024. 11. 16. 12:58
의존성 학습 시리즈 3편 :
(DI - 1편) 의존성 주입이란
(DI - 2편) 안드로이드 의존성 수동 주입
(DI - 3편) DI 라이브러리 Hilthttps://developer.android.com/training/dependency-injection?hl=ko
Android의 종속 항목 삽입 | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Android의 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 종속 항목 삽입(DI)은 프로그래
developer.android.com
안드로이드에서 아키텍처를 학습을 하다 보니 자주 보는 것이 DI(Dependency Injection)였다.
Dependency Injection을 한국말로 하면 '의존성 주입' 이라고 하는 것 같다.
여러가지 예제 안드로이드 프로젝트를 봤을때 주로 MVVM나 앱 아키텍처(주로 3layer 아키텍처)를 적용하면서 Dagger Hilt 라이브러리가 많이 포함되어 있었다.
아마도 안드로이드 구글에서 의존성 라이브러리인 Dagger Hilt를 사용하는 것을 추천하기 때문인 것 같다.
Hilt를 학습하면서 이번에 의존성 주입이 무엇인지 한번 정리해보려고 한다.
의존성 삽입이 뭔지 알기위해서는 일단 의존성이 뭔지를 알아야 삽입하건 말건 할것이다. 그래서 먼저 의존성이 무엇인지 좀 알아봐야겠다.
1. 의존성이란?
의존성은 한 코드나 모듈이 다른 코드, 라이브러리, 또는 외부 자원을 사용하거나 필요로 하는 관계를 의미합니다.
어떤 클래스나 객체가 기능을 수행하려면 외부에서 제공되야 하는 특정 서비스나 데이터가 필요할 때, 그 필요를 의존성이라고 한다.
안드로이드에서 프로그래밍을 기준으로 예시를 든다면, Activity 에서 ViewModel Class의 필요를 의존성이라고 할 수 있고
Activity는 ViewModel에 의존성을 가진다고 할 수 있다.
Class가 필요한 객체를 얻는 세 가지 방법은 다음과 같습니다.
- 직접 생성 - 클래스가 직접 인스턴스를 생성 (예: Engine engine = new Engine()).
- 외부 획득 - Android API처럼 Context에서 가져옴 (예: getSystemService()).
- 의존성 주입 - 생성자 또는 메서드를 통해 매개변수로 제공받음 (예: Car(Engine engine)).
여기서 직접 생성하는 것과 의존성 주입을 통해서 하는 것이 어떻게 다른지 좀더 자세히 알아보자
class Car { private Engine engine = new Engine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
Car 클래스에서 Engine은 직접 호출하고 있다. 이것을 "Car 클래스는 Engine 클래스에 의존성을 가진다" 라고 합니다.
하지만 이런 방식은 생성자나 setter 방식으로 객체를 받아서 사용하는 것이 아닌 class를 직접 참조해 사용하는 방식은 문제점이 있다.
- 1. 확장성 제한:
- 직접 참조해서 사용했기 때문에 다른 Engine을 사용하려는 기능적 확장을 원한다면 Car class의 코드를 추가하거나 변경해야 한다.
- 2. 재사용성 저하 :
- Car 클래스를 다양한 Engine과 사용하려면 그때마다 Car 클래스를 만들어야 한다. 만약 생성자나 setter메소드로 받았다면 하나의 Car 클래스로 다양한 Engine을 사용할 수 있었을 것이다.
- 3. 테스트의 어려움 :
- 테스트를 할때 클래스 간의 의존성이 높아 단위 테스트 작성이 어려워진다.
- (이 부분은 아직 유닛테스트를 학습하기 전이어서 유닛테스트 학습을 하면서 좀더 공부를 해봐야 할 것 같다.)
class가 다른 class를 직접 참조하는 것은 여러가지 문제점이 있기 때문에 의존성 주입 (DI : Dependency Injection) 이다.
2. 의존성 주입(DI : Dependency Injection) 이란?
의존성 주입은 객체가 필요로 하는 의존성을 외부에서 주입받는 설계 패턴이다. 객체 스스로가 의존성을 직접 생성하지 않고, 필요한 의존성을 외부에서 제공 받아 사용하는 방식이다.
class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.start(); } }
위와 같은 방식으로 생성자로 객체를 넘겨받아 사용하는 것을 의존성 주입이라고 하며 이렇게 한다면 Car의 재사용 할수 있는 방법이 다양해진다. Engine의 다양한 구현을 Car에 전달할 수 있기 때문이다.
Engine유형이 아닌 Electric 유형의 엔진의 차를 만든다면 Car의 변경없이 재활용하여 Engine을 외부에서 전달해주기만 하면 됩니다.
의존성 주입의 장점
- 코드의 재사용성, 유연성이 높아진다.
- 확장성을 가진다
- 유지보수가 쉬우며 테스트가 용이해진다
의존성 주입의 단점
- 책임이 분리되어 있기 때문에 클래스 수를 늘림으로써 복잡성이 증가한다.
- 주입된 객체들에 관한 코드 추적이 어렵다.
- 초기 개발 노력이 필요하다.
- 의존성 주입 프레임워크를 사용하면 빌드 시간이 늘어날 수 있으며, 프레임워크에 대한 의존도를 높인다.
3. 주요 의존성 주입 방법
라이브러리 사용 없이 외부에서 의존성을 주입하는 주요한 2가지 방법이 있다.
- 생성자
- 필드 (setter 삽입)
생성자는 이미 위에 예제가 있어서 필드 부분만 추가했다.
더보기class Car { private Engine engine; public void setEngine(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.setEngine(new Engine()); car.start(); } }
위의 방식들은 라이브러리를 사용하지 않고 직접 생성하고 관리하는 방식이다. 이것을 dependency injection 또는 manual dependency injection이라고 한다.이런 수동적인 방식은 몇가지 문제가 있다.
1) 대규모 앱의 경우 모든 종속 항목을 올바르게 연결하려면 대량의 상용구 코드가 필요함.
2) 종속 항목을 전달하기 전에 수정할 수 없을 때는 메모리에서 종속 항목의 전체 기간을 관리하는 맞춤 컨테이너를 작성하고 유지해야함.
종속 항목을 생성하고 제공하는 프로세스를 자동화하여 이 문제를 해결하는 라이브러리가 있다. 이런 라이브러리들은 2가지 카테고리로 나뉜다.
- 런타임 시 종속 항목을 연결하는 리플렉션 기반 솔루션
- 컴파일 시간에 종속 항목을 연결하는 코드를 생성하는 정적 솔루션
더보기1. 런타임 시 종속 항목을 연결하는 리플렉션 기반 솔루션
리플렉션을 사용해 런타임 시 의존성을 주입하는 방식은 유연하고 동적인 연결이 가능하지만, 컴파일 시점에 확인되지 않고, 리플렉션을 사용하는 만큼 성능에 영향을 줄 수 있습니다.
- Spring Framework: Spring은 @Autowired, @Inject 등의 어노테이션을 통해 의존성 주입을 지원하며, 주로 런타임 시 리플렉션을 통해 의존성을 주입합니다.
- Guice: Google Guice는 주로 리플렉션 기반으로 의존성을 관리하며, @Inject 어노테이션과 모듈을 통해 의존성을 런타임에 주입합니다.
- HK2: Java에서 JSR-330을 기반으로 하는 의존성 주입 프레임워크로, 주로 런타임에 리플렉션으로 의존성을 연결합니다.
2. 컴파일 시간에 종속 항목을 연결하는 정적 솔루션
컴파일 시점에 의존성 주입 코드를 생성하는 정적 솔루션은 빠른 실행 속도와 더 강력한 타입 안정성을 제공합니다. 리플렉션을 사용하지 않기 때문에 성능상 이점이 크지만, 유연성 측면에서는 리플렉션 기반보다 제한적일 수 있습니다.
- Dagger (Dagger 2, Dagger Hilt): Dagger는 Square와 Google에서 개발한 컴파일 타임 의존성 주입 프레임워크로, 주로 @Inject, @Component 등을 활용해 컴파일 시 의존성 주입 코드를 생성합니다. Dagger는 리플렉션을 사용하지 않으며, 성능이 뛰어나기 때문에 안드로이드에서 자주 사용됩니다.
- Kodein: Kotlin 전용 의존성 주입 라이브러리로, 안드로이드와 JVM 환경에서 사용 가능하며 컴파일 타임에 의존성 주입 코드를 생성할 수 있습니다. Kodein은 런타임 성능이 우수하지만, 유연성 측면에서 Spring이나 Guice보다 제한적일 수 있습니다.
Hilt
Hilt는 Android에서 종속 항목을 삽입하기 위한 Jetpack의 권장 라이브러리다 Hilt는 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 실행하는 표준 방법을 정의합니다. Hilt는 Dagger가 제공하는 컴파일 시간 정확성, 런타임 성능, 확장성 및 Android 스튜디오 지원의 이점을 누리기 위해 인기 있는 DI 라이브러리인 Dagger를 기반으로 빌드되었다.
다음번에는 Dagger Hilt에 대해 더 자세히 알아보려고 한다.
끝내기 전에 의존성 주입에 대해 생각해볼 지점이 있어 적어본다.
4. 의존성 주입 적용에 참고 사항
프로젝트 완성 이후 유지보수의 필요가 없고 간단한 프로그램을 만들 경우 의존성 주입은 오히려 번거롭고 불필요한 선택일 수 있다.
또한, 개발자나 팀이 의존성 주입이라는 개념에 익숙하지 않거나 숙련되지 않은 경우 의존성 주입 프레임워크에 대한 학습이 필요하기 때문에 개발에 소요되는 시간이 늘어날 수 있다. 개발 완성 이후 유지보수보다 정해진 기간내에 개발을 빠르게 진행해야 하는 프로젝트에는 맞지 않을수도 있다.
위와 같은 경우가 아니라 장기적인 관점에서 본다면 상용 애플리케이션을 만들고 서비스를 하는 시간이 길어질수록 추가 기능 개발이나 수정등에 이유로 애플리케이션이 복잡해진다면 의존성 주입은 빛을 발하고 장기적으로 생산성 향상에 도움이 될 것이다.
그래서 프로젝트를 시작하기 전에 중요한 점을 먼저 체크하고 의존성 주입을 프로젝트에 반영할 것인지 결정해야 할 것이다.'안드로이드 학습 > Android 기술면접 대비' 카테고리의 다른 글
안드로이드 Coroutine Flow - 1 (Flow란?) (0) 2024.11.27 안드로이드 코루틴 (Coroutine Builder) 코드 (0) 2024.11.18 (DI - 3편) DI 라이브러리 Hilt Annotations (0) 2024.11.16 (DI - 2편) 안드로이드 의존성 수동 주입 (0) 2024.11.16 안드로이드 코루틴 Scope (0) 2024.11.15