[Effective Kotlin] 아이템 35. 복잡한 객체를 생성하기 위한 DSL을 정의하라

Jimin Lim·2023년 9월 6일
0

Effective Kotlin

목록 보기
35/39
post-thumbnail

아이템 35

복잡한 객체를 생성하기 위한 DSL을 정의하라

DSL은 복잡한 객체, 계층 구조를 갖고 있는 객체를 정의할 때 유용하다.

예를 들어, Ktor를 이용한 API를 다음과 같이 작성할 수 있다.

fun Routing.api() {
    route("news") {
        get {
            val newsData = NewsUseCase.getAcceptedNews()
            call.respond(newsData)
        }
        get("propositions") {
            requireSecret()
            val newsData = NewsUseCase.getPropositions()
            call.respond(newsData)
        }
    }
    // ...
}

사용자 정의 DSL 만들기

함수 타입

함수 타입은 (인자) -> 반환 형식을 가진다. 아래는 그 예시이다.

  • ()->Unit: 아규먼트를 갖지 않고, Unit을 리턴하는 함수
  • (Int)->()-> Unit: Int를 아규먼트로 받고, 다른 함수를 리턴한다. 이 때 다른 함수는 아규먼트로 아무것도 받지 않고, Unit을 리턴한다.
  • (()->Unit)->Unit: 다른 함수를 아규먼트로 받고, Unit을 리턴, 이때 다른 함수는 아규먼트로 아무것도 받지 않고, Unit을 리턴한다.

함수 타입 예시

fun plus(a: Int, b: Int) = a + b 이 함수를 다음과 같이 표현할 수 있다.

//리시버를 가진 함수 타입
val myPlus: Int.(Int) -> Int = fun Int.(other:Int) = this + other

//람다 표현식
val myPlus: Int.(Int)->Int = { this + it }

위와 같이 리시버를 가진 익명 확장 함수와 람다 표현식은 다음과 같은 방법으로 호출할 수 있다.

//일반적인 객체처럼 invoke 사용
myPlus.invoke(1,2)

//확장함수가 아닌 함수처럼 사용
myPlus(1,2)

//일반적인 확장함수처럼 사용
1.myPlus(2)

DSL 생성 예시

fun createTable(): TableBuilder = table {
    tr {
        for (i in 1..2) {
            td {
                +"This is column$i"
            }
        }
    }
}

fun table(init: TableBuilder.() -> Unit): TableBuilder {
    ....
}

class TableBuilder {
    fun tr(init: TrBuilder.() -> Unit) {...}
}

class TrBuilder {
    fun td(init: TdBuilder.() -> Unit) {...}
}

class TdBuilder {
    var text = ""
    operator fun String.unaryPlus() {
        text += this
    }
}

table 함수의 아규먼트는 tr함수를 갖는 리시버를 가지도록 작성한다. tr도 마찬가지!

정리

  • 복잡한 자료구조
  • 계층적인 구조
  • 거대한 양의 데이터

위와 같은 경우에 사용하면 좋다. 간단한 곳에 사용하면 닭 잡는 곳에 소 잡는 칼 쓰는 꼴이다.

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글