Hello, Freakin world!

코틀린으로 ModelMapper 만들기 - 2 본문

프로그래밍 언어/코틀린

코틀린으로 ModelMapper 만들기 - 2

johnna_endure 2022. 3. 30. 00:52

우선 1번 글에서 지적한 문제를 해결한 코드부터 살펴보겠습니다.

 

import kotlin.reflect.KParameter

class ModelMapper {

    /**
     * 반환 클래스의 주 생성자를 통해 값을 바인딩하고 반환하는 mapper
     */
    inline fun <reified T, reified R> mapper(source: T): R {
        R::class.constructors
            .last { constructor ->
                val mutableMap: MutableMap<KParameter, Any?> = mutableMapOf<KParameter, Any?>()
                constructor.parameters.forEach { parameter ->
                    mutableMap[parameter] = T::class.members.find { it.name == parameter.name }?.call(source)
                }
                return constructor.callBy(mutableMap)
            }
        throw RuntimeException("${R::class.simpleName} 클래스의 생성자가 존재하지 않습니다.")
    }
}

inline, reified 같은 자바에서는 볼 수 었었던 키워드들이 보이네요.

일단 이것들은 제쳐두고 중괄호 블록 안의 코드에 대해서 잠깐 설명을 해볼게요.

 

코틀린에선  :: 연산자를 통해 리플렉션 메서드를 호출할 수 있습니다.

아래는 타입파라미터 R 클래스의 생성자 중 가장 마지막 생성자에 접근해 람다를 파라미터로 주는 코드입니다.

 

주생성자가 있는 경우 마지막 생성자는 주생성자가 있을 경우, 항상 주생성자가 됩니다.

(* 코틀린에서 주생성자는 class 이름 옆에 바로 선언되는 생성자를 주생성자라고 합니다.)

 

R은 리턴될 타입을 가리키는 파라미터입니다.

대게 주생성자만 있는 data class인 경우가 많기 때문에 일단은 이런 식으로 구현했습니다.

 R::class.constructors.last { constructor => ... }

 

그런데 잠깐!

뭔가 이상합니다. 자바에선 이러한 접근이 불가능하지 않았었나요?

자바에선 타입 파라미터에 리플렉션 API 접근이 불가능합니다.

제너릭 타입 이레이즈(Gereric type erase) 라는 과정이 컴파일에 포함돼있기 때문입니다. 이 과정은 런타임에 타입 정보를 지워버립니다.

그래서 자바로 코드를 작성할 때는 R로 타입을 지정하기만 할 뿐, R 타입에 리플렉션 API를 호출할 수가 없습니다.

 

하지만 코틀린에서는 가능합니다!

뭣때문에 가능하냐면 앞서 언급했던 inline, refied 라는 키워드 때문입니다.

inline 키워드는 함수 앞에 사용할 수 있습니다.

이를 인라인 함수라고 하는데, 이는 함수를 호출하는 곳에 함수 본문을 컴파일 시점에 끼워넣어버립니다.

바로 이 점 때문에, 코틀린은 타입 파라미터에 어떤 타입이 할당될 것인지 문맥을 유추할 수 있게 됩니다.

 

마치 런타임에 타입이 할당되는 것처럼, 타입 파라미터에 리플렉션 API를 호출해서 위와 같은 작업을 할 수 있는것이지요.

그리고 인라인 함수의 제네릭 타입에는 reified라는 키워드를 붙여줘야 합니다.

 

위의 저 함수 하나로 간단한 모델 매퍼를 만들어 봤는데요. 

아래는 모델 매퍼를 사용하는 간단한 예입니다.

 

    data class UserModel(
        val name: String,
        val age: Int
    )

    
    fun findUser(id: Long) : UserModel {
        return userRepository.findById(id)
            .map { modelMapper.mapper<User, UserModel>(it) }
            .orElseThrow { NotFoundDataException("해당 아이디의 사용자 정보를 찾을 수 없습니다. id : $id") }
    }

data class는 더이상 리플렉션을 위한 의미없는 인자가 없는 생성자를 생성할 필요가 없습니다.

단지 객체를 초기화할 필드를 모두 주생성자에 포함시키면 됩니다.

 

자바에선 mapper 인자에 UserModel.class 처럼 타입에 대한 정보를 Class<T> 로 받았지만, inline, reified 키워드로 이 역시도 필요가 없어졌습니다. 

단지 파라미터를 전달하는 것만으로 이를 대체할 수 있습니다!

'프로그래밍 언어 > 코틀린' 카테고리의 다른 글

코틀린으로 ModelMapper 만들기 - 1  (0) 2022.03.17
Comments