틀린 내용이 있으면 지적해주세요 ㅠㅠ
들어가기 전
앞서 말한 IOC가 왜 필요한 것일까? 많은 이유가 있겠지만, DI의 필요성 위주로 설명해 보겠다.
OOP는 관심사 분리를 통한 Decoupling을 통해서 유지보수나 테스트 등에서 이점을 볼 수 있다.
간단한 예를 통해서 관심사 분리의 이점을 확인해 보자.
class GetUserClass(){ // 직관적인 부분만 사용하여 실제 코드와는 많은 차이가 있음
fun getUser(userId: String) : User{
....// 조금 긴 비즈니스 코드
}
}
class Login(){
fun getMyData() : User {
....// 조금 긴 비즈니스 코드
}
}
부분적으로 사용자의 정보를 가져오는 기능이 필요한 서로 다른 두 Class이다.
같은 기능이지만, 저렇게 두 개로 나누어서 개발하다 보면 기능이 조금씩 변경될 때마다 관리가 힘들어진다.
아래를 보자.
class GetUserClass(){
fun getUser(userId: String) : UserV2{
....// 조금 긴 비즈니스 코드
...// + User 사진도 같이 관리하게 됨 (URI를 저장했음)
}
}
class Login(){
fun getMyData() : User {
....// 조금 긴 비즈니스 코드
...// + User 사진도 같이 관리 (File로 저장함)
}
}
그럴 일은 없겠지만, 업데이트를 하면서 위의 사용자 받는 방법에서는 URI로 받아오게 하였는데 아래의 객체에서는 URI로 받았던 것을 잊고 File로 받아오게 하였다.
그리고 UserV2를 반환하기로 계획을 했지만, 잊고 User DataClass를 수정하여 File을 추가하여 관리를 하였다.
극단적인 예로 10개의 Class에서 따로 이 기능을 관리한다고 하면, 업데이트 시에 10개를 동일하게 또는 비슷하게 수정하여야 오류를 피할 수 있다.
그럼 관심사를 분리해보자.
Class ObjectOnlyForGetUser(){
operator fun invoke(id : String) = ...
}
Class GetUserClass(){
fun getUser() = GetUser(id)
}
Class Login(){
fun getUser() = GetUser(id)
}
이렇게 하면 하나의 ObjectOnlyForGetUser만을 관리하여 유지 보수가 편리하게 이루어지고, 각 사용 객체들도 자신의 책임이 덜어지게 된다.
이번 글에서 소개한 두 방법에 대해서 어떤 것이 좋고 나쁘다를 말하기보다는, 두 방식 간의 차이를 이해하는 것이 목표이다.
그럼 여기서 위의 책임을 위임한 객체를 어떻게 선언을 할 것일가에 대한 의문이 남아있다.
1. 필요한 객체를 사용하는 객체 내부에서 생성하여 사용한다. (내부 주입이라고 생각해도 될까?)
(Factory도 객체를 Inject 하는 방식이라고 볼 수 있다. 다만 그 Inject하는 책임의 차이가 존재, 객체 스스로냐 [Factory 내부주입], 외부냐 [Hilt 외부주입])
2. 필요한 객체를 외부에서 사용하는 객체에 주입한다( [외부] Dependency Inject)
1. 필요한 객체를 사용하는 객체 내부에서 생성하기. (Factory)
Class Person(){
fun goHome() {
val transport : Transport = Car()
transport.start()
}
}
귀가하는 사람에게 이동수단을 제공하여 집에 가도록 하였다.
이렇게 만들어도 문제는 없지만 만약에 자동차가 아닌 다른 수단을 이용하는 사람의 경우에는 어떻게 제공해야 할까?
여기서 FactoryMethod가 등장하게 된다.
간단하게 소개하자면, 위의 필요한 객체를 만드는 기능을 다른 객체에 위임하여 생성한다.
Class TransportFactory(){
fun build(type : Type) : Transport{
return when(type){
...
}
}
}
// 객체 생성시
val tf = TransportFactory()
tf.build(Car) // 자동자 생성
tf.build(Subway) // 지하철 생성
tf.build(Airplane) // 비행기 생성
Class Person(){
val tf = TransporFactory()
// 이 객체를 DI로 외부주입하는 경우도 꽤나 있더라. 왜 그럴까?
// Interface로 추상화하여 Factory도 유연하게 사용하기 위함일까?..
fun goHome() {
val transport = tf.build(Subwawy)
transport.start()
}
}
Factory에게 생성을 위임하였지만, 여전히 생성에 대한 책임이 사용자에게 있다.
( tf.build()를 Person이 실행하기 때문에 객체 생성을 Person이 함)
Dependency Inject가 객체를 주입한다는 개념에서 Factory도 DI의 방법중 하나라고 하는 글도 봤음
2. 객체 외부에서 사용자에게 주입하기.(DI)
위와는 다르게 객체 외부에서 생성된 객체를 사용자에게 주입하는 방법이다.
생성자를 통해서 객체 내부로 주입하거나.
생성자를 가지지 못하는 객체는 Field주입을 통해서 이용할 수 있다.
Class Person(private val transport : Transport ){
fun goHome() {
transport.start()
}
}
위에 Factory와 다르게 transport객체의 생성에는 Person이 개입하지 않는다. 다만 주입받아 사용할 뿐이다.
3. DI와 Hilt
DI는 위 2)와 같이 객체를 다른 객체에 주입하는 방식을 말하며(일반적으로),
Dagger와 Hilt는 객체 생성을 라이브러리를 통해서 관리해주어 보다 편리하게 사용할 수 있도록 해준다.
아래의 예를 통해서 Hilt적용 시의 장점을 확인해보자.
class MyViewModel(repo : MyRepo) : ViewModel() {}
class MyRepo(dataSource: MyDataSource){}
class MyDataSource(api : Firebase){}
class FirstViewController(){
val api = Firebase.getInstance()
val dataSource = MyDataSource(api)
val repo = MyRepo(dataSource)
val vm = MyViewModel(repo)
vm....
}
class SecondViewController(){
val api = Firebase.getInstance()
val dataSource = MyDataSource(api)
val repo = MyRepo(dataSource)
val vm = MyViewModel(repo)
vm....
}
repo나 api 등을 싱글톤으로 관리하거나,
다른 객체들의 Lifecycle을 조정하는 등의 작업을 수동으로 관리를 하여 개발을 할 수도 있지만,
이를 자동으로 관리해주는 Hilt를 통해서 객체 생성의 상용구를 줄이거나 생명주기 컨트롤이 용이해진다.
(자동이라기 보다는 개발자의 모듈 정의에 의해서..)
그냥 아래와 같이 선언할 수 있다.
class SecondViewController(){
val vm : MyViewModel by viewmodel()
}
이는 Module에 필요로 하는 객체의 생성 방법을 정의해 주는 과정과 Hilt라이브러리를 사용하기 위한 규칙을 준수해야 함.
Hilt는 DI를 쉽고 편리하게 관리할 수 있도록 만들어주는 라이브러리이다.
4. ServiceLocator
다른 객체를 사용자 객체가 사용할 때, 필요한 객체를 Object로 선언하여 생성에 관여하지 않고 있는 객체를 사용하는 방법이다.
좋은 접근이라고 볼 수 있지만, 위의 객체 LifeCycle관리가 힘들고,
객체를 생성해놨지만, 이 객체를 사용하는 객체들의 관리도 힘들다. 테스트도 영...
정리
Factory와 DI는 IOC에 포함되고, 객체 생성의 책임이 내부에 있는가 외부에 있는가의 차이가 있다.
Factory와 DI를 통해서 관심사 분리를 하여 코드의 재사용성을 높이고, 유지 보수를 향상할 수 있다.
DI를 수동으로 사용하려면 많은 Boillerplate가 생기게 되고, 객체의 생명주기에 대한 고려도 사용자가 직접 신경 써야 한다.
자동으로 DI를 해 줄 수 있는 Hilt 라이브러리를 사용한다면, Boilerplate를 줄이고, 생명주기에 대한 고려도 덜 수 있다.
Hilt를 통한 어플리케이션 제작을 강추한다.
'안드로이드 읽어보기 > 6. DI(Hilt)' 카테고리의 다른 글
1. IOC(Inversion of control) (0) | 2022.05.19 |
---|