🎨 Créer un DSL
📖 Qu'est-ce qu'un DSL ?
DSL (Domain-Specific Language) est un langage spécialisé pour un domaine spécifique. Avec Kotlin, vous pouvez créer des API lisibles et intuitives !
💡 Concepts de base
Lambda et récepteur
// Lambda ordinaire
fun buildString1(action: () -> String): String {
return action()
}
// Lambda avec récepteur
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 et with
class Person {
var name: String = ""
var age: Int = 0
}
fun main() {
// apply - renvoie l'objet
val person1 = Person().apply {
name = "홍길동"
age = 25
}
// with - renvoie le résultat
val greeting = with(person1) {
"안녕하세요, ${name}님! (${age}세)"
}
println(greeting)
}
🎯 DSL HTML
Constructeur HTML simple
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)
}
Ajout d'attributs
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>
}
🔧 DSL SQL
Constructeur de requêtes
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 de configuration
Configuration d'application
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 de test
Framework de test simple
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("\nRésultat : $passed réussi(s), $failed échoué(s)")
}
}
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
}
}
}
🏗️ Pattern Builder
Constructeur d'utilisateur
class User private constructor(
val name: String,
val email: String?,
val age: Int?,
val address: String?
) {
class Builder {
private var name: String = ""
private var email: String? = null
private var age: Int? = null
private var address: String? = null
fun name(name: String) = apply { this.name = name }
fun email(email: String) = apply { this.email = email }
fun age(age: Int) = apply { this.age = age }
fun address(address: String) = apply { this.address = address }
fun build(): User {
require(name.isNotBlank()) { "Name is required" }
return User(name, email, age, address)
}
}
override fun toString() = "User(name=$name, email=$email, age=$age, address=$address)"
}
fun user(block: User.Builder.() -> Unit): User {
return User.Builder().apply(block).build()
}
fun main() {
val user = user {
name("홍길동")
email("hong@example.com")
age(25)
address("서울")
}
println(user)
}
🤔 Questions fréquentes
Q1. Quand utiliser un DSL ?
R : Pour rendre élégantes les tâches répétitives !
// ❌ Code verbeux
val config = Config()
config.setHost("localhost")
config.setPort(8080)
config.addRoute("/api", "GET", "handler")
// ✅ DSL
val config = configure {
host = "localhost"
port = 8080
route("/api") {
method = "GET"
handler = "handler"
}
}
Q2. Qu'est-ce que @DslMarker ?
R : Restriction des DSL imbriqués !
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HtmlTag {
fun div(block: HtmlTag.() -> Unit) { }
}
fun html(block: HtmlTag.() -> Unit) {
val tag = HtmlTag()
tag.block()
}
Q3. Qu'en est-il des performances ?
R : Pratiquement identiques au code ordinaire !
// Génère presque le même bytecode après compilation
val result1 = buildString {
append("Hello")
}
val builder = StringBuilder()
builder.append("Hello")
val result2 = builder.toString()
🎬 Conclusion
Créez de belles API avec les DSL !
Points clés :
✅ Utiliser les lambdas avec récepteur
✅ Pattern builder avec apply et with
✅ Syntaxe intuitive
✅ Maintenir la sécurité de type
✅ Langage spécifique au domaine
Félicitations ! Vous avez terminé la série Practical ! 🎉
Étape suivante : Apprenez la programmation asynchrone dans Coroutines !