Создание типобезопасного DSL для HTML/конфигурации
Реализуйте простой DSL конфигурации с помощью шаблонов построителей и лямбд-расширений.
«Создание типобезопасного 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 — локальная установка не требуется.
Все уроки этого курса
- Лямбда с получателем: основа DSL
- @DslMarker: предотвращение утечки получателя
- Создание типобезопасного DSL для HTML/конфигурации
- Тестирование и развитие DSL без нарушения совместимости