1. API에서 DSL로
깔끔한 API란 어떤 의미일까?
- 코드 읽는 독자가 명확하게 이해할 수 있어야 한다(이름, 개념)
- 코드가 간결해야 한다.
코틀린의 깔끔한 API를 다시 복습해보자
자바 코틀린 사용한 언어 특성
1-1. DSL : 영역 특화(declarative) 언어
영역 특화 언어 : 범용 프로그래밍 언어를 기반으로 하여, 필요하지 않은 기능을 없앰
→ 범용(imperative) 프로그래밍 언어와 달리 declarative 이다. 하지만, 범용 언어와 조합하기 어렵다. 이를 극복하기 위해 internal DSL 개념이 유명해졌다!
- 예) SQL, 정규식
1-2. 내부 DSL (internal DSL)
- external DSL : 독립적인 문법구조
- internal DSL : 범용 언어로 작성된 프로그램
- 예시(기존SQL)
SELECT Country.name, COUNT(Customer.id) FROM Country JOIN Customer ON Country.id = Customer.country_id GROUP BY Country.name ORDER BY COUNT(Customer.id) DESC LIMIT 1
- 예시(코틀린 internal DSL)
(Country join Customer) .slice(Country.name, Count(Customer.id)) .selectAll() .groupBy(Country.name) .orderBy(Count(Customer.id),isAsc = false) .limit(1)
1-3 DSL 구조
- 보통 람다를 중첩시키거나
- dependencies { complie("junit:junit:4.11") complie("com.google.inject:guice:4.1.0") }
- 메소드 호출을 연쇄시키는 방식으로 구조를 만든다.
- str should startWith("kotlin")
2. 구조화된 API 구축 : DSL에서 수신 객체 지정 DSL 사용
수신 객체 지정 람다는 구조화된 API를 만들 때 도움이 되는 강력한 코틀린 기능
→ 구조가 있다는 것은 일반적인 API와 DSL을 구분하는 아주 중요한 특성
예시
- 람다를 인자로 받는 buildString()
- fun buildString(builderAction: (StringBuilder) -> Unit) : String { val sb = StringBuilder() builderAction(sb) return sb.toString() } val s = buildString { it.append("Hello, ") it.append("World!") } println(s)
- 수신 객체 지정 람다를 사용 - it 사용X
- fun buildString( builderAction: StringBuilder.() -> Unit ) : String { val sb = StringBuilder() sb.builderAction() return sb.toString() } val s = buildString { this.append("Hello, ") append("World!") } println(s)
확장 함수 타입을 함수의 매개변수 타입으로 지정함으로써 수신 객체 지정 람다를 인자로 받을 수 있게 되었다. 왜 확장함수나 수신객체지정람다 타입일까?
- 정의된 메소드를 마치 그 클래스 내부에서 호출하듯이 사용할 수 있다.
- invoke convention을 사용하면 객체를 함수처럼 호출할 수 있다.
- class Greeter(val greeting: String) { operator fun invoke(name: String) { println("$greeting, $name!") } } val bavarianGreeter = Greeter("Servus") bavarianGreeter("Dmitry")
실전 코틀린 DSL
1. kotest 맛보기
s should startWith("kot")
- should
- Matcher 인스턴스를 요구한다.
- infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)
- startWith : Matcher를 구현
- interface Matcher<T> { fun test(value: T) } class startWith(val prefix: String) : Matcher<String> { override fun test(value: String) { if(!value.startsWith(prefix)) throw AssertionError("String $value does not start with $prefix") } }
2. 멤버 확장 함수 : SQL을 위한 내부 DSL
- 클래스 안 확장 함수와 확장 프로퍼티 선언
'공부 > Kotlin' 카테고리의 다른 글
왜 Kotest를 사용해야 할까? (2) | 2022.07.26 |
---|---|
Kotest의 테스트스타일 10가지 (0) | 2022.07.26 |
10장 애노테이션과 리플렉션 (0) | 2022.07.18 |
9장 제네릭스 (0) | 2022.07.02 |
[Kotlin] 파라미터와 반환 값으로 람다 사용해보기 (0) | 2022.07.02 |
댓글