-
DI란 무엇인가.... feat. Hilt, KoinAndroid📱 2022. 10. 19. 00:04
DI란 무엇인가!!
안드로이드 개발을 하면 Hilt와 Koin등을 사용하며 DI라는 개념에 대해서 알게 된다.
정확히 DI는 왜 하는 것이며 어떤것인지 알아보자
DI?
DI(Dependency Injection)은 의존성 주입하고 한다.
의존성 주입은 단어 그대로 하나의 객체가 다른 객체의 의존성을 제공하는 기술이다.
쉽게 단어 그대로 객체 안에서 사용할 객체를 생성하는 것이 아니라 외부에서 생성된 객체를 주입받아서 사용하는 방식이다.
공식 문서에도
Dependency injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, you lay the groundwork for good app architecture.
이처럼 안드로이드 개발에도 DI는 아키텍처를 구성하는데 아주 유용한 존재라고 명시되어있다.
DI의 장점
왜 공식문서에서 까지 DI에 대한 언급이 있는 것일까?
DI는 명확한 장점이 존재한다.
- 코드의 재사용성 증가
- 리팩토링의 용이성 증가
- 테스트의 용이성 증가
코드의 재사용성이 증가한다는 것은 결합도를 낮춘다는 것이고,
결합도가 내려가면 역할과 책임이 분리가 잘되고,
이는 리팩토링과 유지보수가 용이해지는 것,
또 나아가서는 확장성까지도 좋아질 수 있음을 의미한다.
분리가 잘되니 당연히 테스트도 용이해 질수있는 것이다.
공식문서에 나와있는 예를 보면 조금 더 이해가 쉽다.
예시 코드
class Car { private val engine = Engine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
위 코드는 의존성주입이 된 코드가 아니라고 한다.
Car라는 class에서 Engine을 직접 만들어서 사용하고 있기 때문이다.
위에 코드에서 ElectricEngine을 사용해야 하거나 GasEngine을 사용해야 한다면 어떻게 해야 할까?
그렇다면 ElectricEngine을 장착한 Car, GasEngine을 장착한 Car를 또 만들어야 할 것이다.
이게 바로 결합도가 높은 코드가 되는 것이다.
저 상황이 된다면 새로운 엔진을 써야 할 때마다 같은 코드를 계속 써야 하는 상황이 발생하고, Engine을 테스트하기도 힘들어진다.
그렇다면 어떻게 해결을 해야 할까?
DI 방법은 크게 2가지가 있다고 한다.
1. 생성자 주입
class Car(private val engine: Engine) { fun start() { engine.start() } } fun main(args: Array) { val engine = Engine() val car = Car(engine) car.start() }
위 코드를 보자
Car를 생성할 때 main에서 Engine을 만들고 생성자로 주입을 하였다.
이렇게 한다면 Engine을 interface로 만들고, Engine을 상속받아 Gas, Electric 등등의 Engine을 만들어서 생성자로 주입을 한다면
위에서 말했던 문제가 사라지게 된다.
그리고 결합도도 떨어져서 Engine을 테스트할 때도 조금 더 쉬워지게 된다.
2. 새터 주입
class Car { lateinit var engine: Engine fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.engine = Engine() car.start() }
이 방식은 Engine의 setter를 이용해서 직접 초기화를 시키는 방식이다.
이 방식도 이용할 때 초기화할 객체를 선택할 수 있으니 interface를 이용해서 문제를 해결할 수 있게 된다.
대안. 서비스 로케이터
object ServiceLocator { fun getEngine(): Engine = Engine() } class Car { private val engine = ServiceLocator.getEngine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
공식문서에서 DI의 대안으로 언급한 방식은 서비스 로케이터 패턴을 이용한 방식이다.
클래스가 직접 제어 권한을 가지며 사용할 개체를 골라서 이용하는 방식이다.
하지만 이 방식은 단점이 존재한다.
- 모든 테스트가 동일한 서비스 로케이터와 상호작용을 해야 하기 때문에 테스트가 어렵다.
- 외부에서 클래스에 필요한 것이 무엇인지 바로 알기가 어려워지고, 실수의 가능성이 높아진다.
- 다른 생명주기를 범위로 정하거나 한다면 개체의 수명관리가 어려워진다
그래서 서비스 로케이터보다는 의존성 주입을 더 추천하는 뉘앙스다.
근데 우리는 개발을 하면서 많은 의존성 주입을 사용해야 하는데
일일이 다 작성한다면 그게 더 실수를 유발할 가능성이 크지 않을까?
그래서 안드로이드 개발에서 DI를 할 때 주로 사용하는 라이브러리들이 있다.
바로
Dagger, Hilt, Koin이다....
이 부분은 다음 글에서....
'Android📱' 카테고리의 다른 글
@JvmStatic 어노테이션 (0) 2023.01.08 Compose 상태관리 Stateful...? (0) 2022.10.25 [Android] Retrofit2 사용 시 헤더에 데이터 넣기! (0) 2022.10.17 [Compose] hiltViewModel을 써서 composable끼리 viewModel을 공유해보자! (4) 2022.09.20 [Android] Retrofit2으로 서버에 Multipart로 이미지 전송하기 (0) 2022.08.13