Kotlin Academy · Урок

Создание типобезопасного DSL для HTML/конфигурации

Реализуйте простой DSL конфигурации с помощью шаблонов построителей и лямбд-расширений.

Урок 3 из 414 шагов

«Создание типобезопасного DSL для HTML/конфигурации» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Цель: практичный DSL

Мы создадим минимальный, но реалистичный DSL для построения HTML на Kotlin. Те же шаблоны применимы к любой конфигурации, тестовому набору данных или построителю отчётов. В результате получится типобезопасный API, удобный для IDE и заменяющий конкатенацию строк.

Определение базового класса Tag

Все элементы HTML имеют общую структуру: имя, атрибуты и дочерние элементы. Начните с базового класса:

open class Tag(val name: String) {
    val attrs = mutableMapOf<String, String>()
    val children = mutableListOf<Tag>()
    fun render(indent: Int = 0): String {
        val pad = " ".repeat(indent)
        val attrStr = if (attrs.isEmpty()) "" else " " + attrs.entries.joinToString(" ") { "${it.key}=\"${it.value}\"" }
        val body = children.joinToString("\n") { it.render(indent + 2) }
        return "$pad<$name$attrStr>\n$body\n$pad</$name>"
    }
}

Применение @DslMarker

Пометьте все классы-построители, чтобы внутренние области видимости не могли случайно вызывать методы из внешних областей:

@DslMarker
annotation class HtmlDsl

@HtmlDsl class Html : Tag("html")
@HtmlDsl class Head : Tag("head")
@HtmlDsl class Body : Tag("body")
@HtmlDsl class Div  : Tag("div")
@HtmlDsl class P   : Tag("p")

Добавление функций-построителей

Каждый класс тега получает функцию-расширение, которая создаёт дочерний тег, применяет блок и добавляет его к дочерним элементам:

fun Html.head(block: Head.() -> Unit) = Head().also { it.block(); children += it }
fun Html.body(block: Body.() -> Unit) = Body().also { it.block(); children += it }
fun Body.div(block: Div.() -> Unit) = Div().also { it.block(); children += it }
fun Div.p(text: String) = P().also { it.attrs["text"] = text; children += it }

Функция html верхнего уровня

Предоставьте точку входа верхнего уровня, которая создаёт корневой элемент и применяет пользовательский блок:

fun html(block: Html.() -> Unit): Html {
    val root = Html()
    root.block()
    return root
}

Использование DSL

В результате получается чистый и удобный DSL:

val page = html {
    head { }
    body {
        div {
            p("Hello, DSL!")
        }
    }
}
println(page.render())

Добавление атрибутов

Добавьте вспомогательную функцию attr для удобной установки произвольных атрибутов:

fun Tag.attr(key: String, value: String) { attrs[key] = value }

// Usage
div {
    attr("class", "container")
    p("Content")
}

Шаблон DSL конфигурации

Тот же шаблон работает для объектов конфигурации. Вместо HTML-тегов используйте классы предметной области:

class ServerConfig {
    var host = "localhost"
    var port = 8080
    var db: DbConfig? = null
    fun database(block: DbConfig.() -> Unit) { db = DbConfig().apply(block) }
}
fun server(block: ServerConfig.() -> Unit) = ServerConfig().apply(block)

Вложение и композиция

Поскольку каждая функция-построитель возвращает созданный объект, вы можете свободно объединять построители. Глубина вложения DSL соответствует глубине иерархии данных.

Предотвращение ошибок с помощью типов

Система типов обеспечивает соблюдение структуры: нельзя вызвать body { } внутри Head, потому что у Head нет функции-расширения body. Недопустимые структуры вызывают ошибки во время компиляции.

Визуализация и тестирование

Поскольку DSL создаёт обычное дерево объектов Kotlin, визуализация и тестирование становятся простыми: вызовите render() и проверьте строку результата либо обойдите дерево объектов в тестах.

Дальнейшее расширение DSL

Для добавления новых тегов или параметров конфигурации достаточно написать новые функции-расширения для соответствующего класса-получателя. Существующие места вызова не затрагиваются — на практике это принцип открытости и закрытости.

Быстрая проверка

Что делает DSL для HTML и конфигурации на Kotlin «типобезопасным»?

Повторение: создание типобезопасного DSL

Основные шаги:

  • Определите классы-построители для каждого уровня иерархии
  • Добавьте аннотацию @DslMarker, чтобы предотвратить утечку контекста получателя
  • Напишите функции-расширения, которые создают, настраивают и добавляют дочерние объекты
  • Предоставьте функцию точки входа верхнего уровня
  • Формирование результата и тестирование выполняются на обычных объектах Kotlin
Можно начать бесплатно

Изучай Kotlin с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «Создание типобезопасного DSL для HTML/конфигурации» бесплатный?

Да — полный текст урока «Создание типобезопасного DSL для HTML/конфигурации» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Создание типобезопасного DSL для HTML/конфигурации»?

Реализуйте простой DSL конфигурации с помощью шаблонов построителей и лямбд-расширений. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Создание типобезопасного DSL для HTML/конфигурации»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Лямбда с получателем: основа DSL
  2. @DslMarker: предотвращение утечки получателя
  3. Создание типобезопасного DSL для HTML/конфигурации
  4. Тестирование и развитие DSL без нарушения совместимости
← Назад к Kotlin Academy