🎨 DSL 만들기
📖 DSL이란?
**DSL(Domain-Specific Language)**은 특정 도메인에 특화된 언어입니다. Kotlin으로 읽기 쉽고 직관적인 API를 만들 수 있습니다!
💡 기본 개념
람다와 수신자
// 일반 람다
fun buildString1(action: () -> String): String {
return action()
}
// 수신자가 있는 람다
fun buildString2(action: StringBuilder.() -> Unit): String {
val builder = StringBuilder()
builder.action()
return builder.toString()
}
fun main() {
val result = buildString2 {
append("Hello ") // this.append("Hello ")
append("World!")
}
println(result) // Hello World!
}
apply와 with
class Person {
var name: String = ""
var age: Int = 0
}
fun main() {
// apply - 객체 반환
val person1 = Person().apply {
name = "홍길동"
age = 25
}
// with - 결과 반환
val greeting = with(person1) {
"안녕하세요, ${name}님! (${age}세)"
}
println(greeting)
}
🎯 HTML DSL
간단한 HTML 빌더
class HtmlBuilder {
private val content = StringBuilder()
fun h1(text: String) {
content.append("<h1>$text</h1>\n")
}
fun p(text: String) {
content.append("<p>$text</p>\n")
}
fun div(block: HtmlBuilder.() -> Unit) {
content.append("<div>\n")
block()
content.append("</div>\n")
}
override fun toString() = content.toString()
}
fun html(block: HtmlBuilder.() -> Unit): String {
val builder = HtmlBuilder()
builder.block()
return builder.toString()
}
fun main() {
val page = html {
h1("Welcome!")
p("This is a paragraph.")
div {
h1("Section")
p("Content here.")
}
}
println(page)
}
속성 추가
class Tag(val name: String) {
private val attributes = mutableMapOf<String, String>()
private val children = mutableListOf<Tag>()
var text: String = ""
fun attribute(key: String, value: String) {
attributes[key] = value
}
operator fun String.unaryPlus() {
text = this
}
override fun toString(): String {
val attrs = attributes.entries.joinToString(" ") { "${it.key}=\"${it.value}\"" }
val attrsString = if (attrs.isNotEmpty()) " $attrs" else ""
return if (children.isEmpty() && text.isEmpty()) {
"<$name$attrsString />"
} else {
val childrenString = children.joinToString("\n") { it.toString() }
val content = if (text.isNotEmpty()) text else childrenString
"<$name$attrsString>$content</$name>"
}
}
}
fun tag(name: String, block: Tag.() -> Unit): Tag {
val tag = Tag(name)
tag.block()
return tag
}
fun main() {
val div = tag("div") {
attribute("class", "container")
+"Hello World"
}
println(div)
// <div class="container">Hello World</div>
}
🔧 SQL DSL
쿼리 빌더
class Query {
private var table: String = ""
private val columns = mutableListOf<String>()
private var whereClause: String = ""
fun from(table: String) {
this.table = table
}
fun select(vararg columns: String) {
this.columns.addAll(columns)
}
fun where(condition: String) {
whereClause = condition
}
fun build(): String {
val cols = if (columns.isEmpty()) "*" else columns.joinToString(", ")
var sql = "SELECT $cols FROM $table"
if (whereClause.isNotEmpty()) {
sql += " WHERE $whereClause"
}
return sql
}
}
fun query(block: Query.() -> Unit): String {
val query = Query()
query.block()
return query.build()
}
fun main() {
val sql = query {
select("id", "name", "email")
from("users")
where("age > 20")
}
println(sql)
// SELECT id, name, email FROM users WHERE age > 20
}
🎨 설정 DSL
앱 설정
class AppConfig {
var host: String = "localhost"
var port: Int = 8080
var debug: Boolean = false
private val routes = mutableListOf<Route>()
fun route(path: String, block: Route.() -> Unit) {
val route = Route(path)
route.block()
routes.add(route)
}
fun showConfig() {
println("=== Config ===")
println("Host: $host")
println("Port: $port")
println("Debug: $debug")
println("\n=== Routes ===")
routes.forEach { println(it) }
}
}
class Route(val path: String) {
var method: String = "GET"
var handler: String = ""
override fun toString() = "$method $path -> $handler"
}
fun configure(block: AppConfig.() -> Unit): AppConfig {
val config = AppConfig()
config.block()
return config
}
fun main() {
val config = configure {
host = "example.com"
port = 3000
debug = true
route("/api/users") {
method = "GET"
handler = "listUsers"
}
route("/api/users") {
method = "POST"
handler = "createUser"
}
}
config.showConfig()
}
🎯 테스트 DSL
간단한 테스트 프레임워크
class TestSuite(val name: String) {
private val tests = mutableListOf<Test>()
fun test(name: String, block: TestContext.() -> Unit) {
tests.add(Test(name, block))
}
fun run() {
println("=== $name ===\n")
var passed = 0
var failed = 0
for (test in tests) {
val context = TestContext()
try {
test.block(context)
println("✓ ${test.name}")
passed++
} catch (e: AssertionError) {
println("✗ ${test.name}: ${e.message}")
failed++
}
}
println("\n결과: $passed passed, $failed failed")
}
}
class Test(val name: String, val block: TestContext.() -> Unit)
class TestContext {
infix fun <T> T.shouldBe(expected: T) {
if (this != expected) {
throw AssertionError("Expected $expected but was $this")
}
}
}
fun describe(name: String, block: TestSuite.() -> Unit) {
val suite = TestSuite(name)
suite.block()
suite.run()
}
fun main() {
describe("Calculator") {
test("addition") {
val result = 2 + 3
result shouldBe 5
}
test("subtraction") {
val result = 5 - 2
result shouldBe 3
}
test("multiplication") {
val result = 3 * 4
result shouldBe 12
}
}
}