복잡한 객체를 생성하기 위한 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)
}
}
// ...
}
함수 타입은 (인자) -> 반환
형식을 가진다. 아래는 그 예시이다.
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)
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도 마찬가지!
위와 같은 경우에 사용하면 좋다. 간단한 곳에 사용하면 닭 잡는 곳에 소 잡는 칼 쓰는 꼴이다.