본문 바로가기

개발/Kotlin

Item 35: Consider defining a DSL for complex object creation

 Kotlin의 기능들은 Domain Specific Language (DSL)을 만드는 데에 도움을 준다. 이러한 DSL은 복잡한 object 혹은 계층적인 구조의 object를 만들 때 유용하다. 이것들은 정의하기는 쉽지 않지만, 한 번 정의해두면 보일러플레이트나 복잡도를 없애주고 개발자의 의도를 명확하게 드러내준다.

 

 DSL을 사용하는 다양한 예시가 있지만, 그 중 많이 사용하는 Gradle DSL을 Gradle configuration을 정의할 때 사용할 수 있다.

Gradle DSL

 복잡하고 계층적인 데이터 구조를 만드는 것이 DSL로 더 쉬워졌다. DSL에서도 코틀린이 제공하는 모든 것들을 사용할 수 있고, Groovy와는 다르게 type-safe하다. 

 

자기만의 DSL 정의하기

 DSL을 만들기 전에 함수 타입의 개념을 먼저 살펴보자. 함수 타입은 함수로서 사용될 수 있는 오브젝트를 나타내는 타입이다. (?) 예를 들어 filter함수에서, 그것은 element가 수용될 수 있는지 혹은 없는지를 결정하는 서술부를 나타낸다.

 다음은 몇 가지 function type의 예시이다.

function type의 인스턴스를 생성하는 가장 기본적인 방법은 다음과 같다.

 fun plus(a: Int, b: Int) = a + b라는 함수가 있을 때, 기존의 함수들은 다음과 같이 만들어졌다.

하지만 우리가 argument 타입을 명시한다면 함수 타입은 추론이 가능하다.

익명함수는 일반 함수와 똑같지만 이름을 가지고 있지 않다.

그렇다면 확장함수에서는 어떨까?

 이것은 우리가 일반 함수에서 이름을 없앤 익명함수를 만들 때 언급이 되었다. 그래서 익명 확장함수는 같은 방식으로 정의된다.

이것의 타입은 어떤 것일까? function type with receiver라는, 확장함수를 나타내는 특별한 타입이 따로 있다. 일반 함수 타입과 비슷해보이지만, 추가적으로 receiver 타입을 argument들 전에 명시한다.

이러한 함수는 lambda 표현식으로 정의될 수 있으며 this 키워드는 extension receiver를 나타낸다. (여기서는 Int형 인스턴스)

익명 확장함수나 receiver가 포함된 람다 표현식으로 만들어진 객체는 3가지 방법으로 invoke 될 수 있다.

 

- 기본적인 object와 같이 invoke 함수를 통해 -> myPlus.invoke(1, 2)

- non-extension function 처럼 -> myPlus(1, 2)

- 일반적인 extension function 처럼 -> 1.myPlus(2)

 

 receiver를 사용한 함수 타입의 가장 중요한 속성은 this가 가리키는 것을 바꾼다는 점이다. this는 apply 함수 내에 있는 인스턴스에서 receiver 오브젝트의 method나 property들에 좀 더 쉽게 접근하게 하기 위해 사용된다.

DSL을 사용해야할 때

 DSL은 정보를 정의하는 방법을 제공해준다. 이를 통해 당신이 원하는 어떠한 정보든지 표현할 수 있도록 해주지만 유저들에게 이 정보들이 추후에 어떻게 사용될지는 명확하게 말해주지 못한다. DSL은 우리가 다른 쉬운 기능을 사용할 수 있다면 오버스펙이 될 수 있다. 하지만 우리가 다음과 같은 것들을 표현해야할 때 유용하다.

 

- 복잡한 데이터 구조

- 계층적인 데이터 구조

- 너무 큰 양의 데이터

 

 모든 것들은 builder나 생성자를 통해 DSL스러운 구조 없이 표현될 수 있다. DSL은 그러한 구조들의 보일러 플레이트를 없애는 것과 관련되어 있다. DSL 사용을 고려할 때는 이러한 보일러 플레이트 코드들이 반복되고, 더 간단한 코틀린 기능이 없을 때 도움이 될 것이다.