Расширенный синтаксис kotlin — реализация и практическая реализация Kotlin DSL

Полное название DSL — доменно-специфический язык, который представляет собой язык, используемый для решения проблем в определенных областях. Язык DSL находится в файле build.gradle при разработке Android.
Kotlin имеет множество языковых функций, которые могут помочь нам реализовать различные языки DSL, такие как:
синтаксис получения и установки, инфиксные функции, перегрузка операторов, функции высшего порядка и лямбда-выражения, функции расширения, функции области видимости и т. д.
Далее мы используем эти функции. для пошаговой реализации DSL для создания карты.Мы
используем следующие классы: класс карты карты, класс слоя карты слоя, интерфейс мониторинга состояния слоя LayerListener, класс источника данных слоя DataSource и визуальный класс карты MapOptions.

class Map {
    
    
    val layerList = mutableListOf<Layer>()
    var mapOptions: MapOptions? = null
    var layerListener: LayerListener? = null
}
interface LayerListener {
    
    
    fun onAdd()
    fun onRemove()
    fun onError()
}
class Layer {
    
    
    var layerName: String? = null
    var dataSource: DataSource? = null
}
class DataSource {
    
    
    var url: String? = null
}
class MapOptions {
    
    
    var centerPoint: Point? = null
    var zoom = 0.0
    var rotate = 0.0

    override fun toString(): String {
    
    
        return "中心点:$centerPoint,缩放:$zoom,旋转:$rotate"
    }

    data class Point(val lat: Double, val lng: Double) {
    
    
        override fun toString(): String {
    
    
            return "经纬度:($lat,$lng)"
        }
    }
}

1. Грамматические особенности функций высшего порядка и лямбда-выражений

Если лямбда-выражение является последним параметром функции высшего порядка, его можно поместить за круглые скобки списка параметров (наиболее важная грамматическая особенность для реализации вложения фигурных скобок); когда лямбда-выражение является единственным параметром функции более высокого порядка . функции заказа, вы можете опустить круглые скобки в списке параметров метода; когда лямбда-выражение имеет и имеет только один параметр, оно может быть заменено им и может быть опущено; лямбда-выражение может указывать получателя, а получатель может используйте это вместо этого и может быть опущено. .
Лямбда-выражения — это ключ к реализации DSL, который позволяет визуально отделять вызовы методов от логических блоков кода.
Пример.
Перед DSL мы создаем MapOptions следующим образом:

val options = MapOptions()
options.centerPoint = MapOptions.Point(355.53, 66.66)
options.zoom = 1.0
options.rotate = 180.0

Мы используем функции высокого порядка для реализации простого DSL следующим образом: Мы помещаем создание объекта в функцию и назначение объекта в лямбда-выражении. Поскольку получатель указан, это можно опустить.

inline fun mapOptions(block: MapOptions.() -> Unit): MapOptions = MapOptions().apply(block)

Используется следующим образом: выглядит нормально, как выдача инструкции по созданию объекта.

mapOptions {
    
    
    centerPoint = MapOptions.Point(355.53, 66.66)
    zoom = 1.0
    rotate = 180.0
}

Давайте добавим тот же DSL для создания в Map следующим образом:

inline fun map(block: Map.() -> Unit): Map = Map().apply(block)

Используйте следующим образом: Таким образом матрешка готова.

map {
    
    
    mapOptions = mapOptions {
    
    
        centerPoint = MapOptions.Point(355.53, 66.66)
        zoom = 1.0
        rotate = 180.0
    }
}

2. Функция расширения

Затем мы устанавливаем слой для карты, поскольку карта использует список для хранения слоя. Недостаточно просто создать слой и реализовать DSL так же, как на первом этапе, а именно:

inline fun dataSource(block: DataSource.() -> Unit): DataSource = DataSource().apply(block)
inline fun layer(block: Layer.() -> Unit): Layer = Layer().apply(block)

Используйте следующим образом: Вы можете видеть, что нам нужно вызвать метод добавления коллекции, поэтому общая структура и читаемость очень плохие.

map {
    
    
    mapOptions = mapOptions {
    
    
        centerPoint = MapOptions.Point(355.53, 66.66)
        zoom = 1.0
        rotate = 180.0
    }
    layerList.add(layer {
    
    
        layerName = "第一图层"
        dataSource = dataSource {
    
    
            url = "https://baidu.com"
        }
    })
}

Мы определенно надеемся, что добавление элементов списка в список и построение объекта можно выполнить только с помощью одной фигурной скобки. В этом случае операцию добавления элементов списка в список нужно вынести отдельно. На данный момент функция расширения может быть использован

inline fun Map.layer(block: Layer.() -> Unit): Layer {
    
    
    return Layer().apply {
    
    
        block()
        layerList.add(this)
    }
}

Используйте следующим образом:

map {
    
    
    mapOptions = mapOptions {
    
    
        centerPoint = MapOptions.Point(355.53, 66.66)
        zoom = 1.0
        rotate = 180.0
    }
    layer {
    
    
        layerName = "第一图层"
        dataSource = dataSource {
    
    
            url = "https://baidu.com"
        }
    }
}

Фактически, этот шаг ничем не отличается от предыдущего. Мы можем поместить функцию слоя непосредственно в класс Map, и эффект будет тот же. Причина, по которой мы используем функцию расширения, заключается в том, что класс Map здесь создается нами самими. Если это сторонняя программа Для классов, которые нельзя изменить в библиотеке, необходимо использовать функции расширения.
Следующим шагом является настройка LayerListener для карты. Его нельзя установить напрямую, поскольку этот интерфейс имеет три метода интерфейса. Мы не можем использовать лямбда-выражения напрямую, поэтому нам нужно разделить три метода интерфейса, то есть делегировать методы интерфейса. Остальные методы мы передаем отдельно при построении интерфейса следующим образом:

class ExtLayerListener : LayerListener {
    
    

    private var addBlock: (() -> Unit)? = null
    private var removeBlock: (() -> Unit)? = null
    private var errorBlock: (() -> Unit)? = null

    override fun onAdd() {
    
    
        addBlock?.let {
    
     it() }
    }

    fun onAdd(block: () -> Unit) {
    
    
        addBlock = block
    }

    override fun onRemove() {
    
    
        removeBlock?.let {
    
     it() }
    }

    fun onRemove(block: () -> Unit) {
    
    
        removeBlock = block
    }

    override fun onError() {
    
    
        errorBlock?.let {
    
     it() }
    }

    fun onError(block: () -> Unit) {
    
    
        errorBlock = block
    }
}
inline fun Map.listener(block: ExtLayerListener.() -> Unit) {
    
    
    layerListener = ExtLayerListener().apply {
    
    
        block()
    }
}


Используйте следующим образом: Довольны ли вы эффектом чтения и простотой?

map {
    
    
    mapOptions = mapOptions {
    
    
        centerPoint = MapOptions.Point(355.53, 66.66)
        zoom = 1.0
        rotate = 180.0
    }
    layer {
    
    
        layerName = "第一图层"
        dataSource = dataSource {
    
    
            url = "https://baidu.com"
        }
    }
    listener {
    
    
        onAdd {
    
    
            Log.e("layer", "新增图层")
        }
        onRemove {
    
    
            Log.e("layer", "移除图层")
        }
        onError {
    
    
            Log.e("layer", "图层错误")
        }
    }
}

3. Используйте инфиксные функции и перегрузку операторов.

Инфиксные функции и перегрузка операторов позволяют нам использовать некоторые специальные операторы и символы для вызова некоторых функций и соединения блоков кода
следующим образом:
Мы добавляем инфиксную функцию в класс MapOptions.

infix fun Point?.to(point: Point?) {
    
    
    centerPoint = point
}

использовать:

mapOptions {
    
    
    centerPoint to MapOptions.Point(355.53, 66.66)
    zoom = 1.0
    rotate = 180.0
}

Мы добавляем перегрузку оператора в класс Layer, чтобы класс Layer мог выполнять операции сложения с классом DataSource.При добавлении DataSource присваивается слою.

operator fun plus(source: DataSource?) {
    
    
    this.dataSource = source
}

Эффект следующий:

layer {
    
    
    layerName = "第一图层"
} + dataSource {
    
    
    url = "https://baidu.com"
}

На этом этапе мы завершили построение класса карты. Полный код я вставлю ниже:

interface LayerListener {
    
    
    fun onAdd()
    fun onRemove()
    fun onError()
}

class Map {
    
    
    val layerList = mutableListOf<Layer>()
    var mapOptions: MapOptions? = null
    var layerListener: LayerListener? = null

infix fun MapOptions?.to(options: MapOptions?) {
    
    
    mapOptions = options
}
}

class Layer {
    
    
    var layerName: String? = null
    var dataSource: DataSource? = null

    operator fun plus(source: DataSource?) {
    
    
        this.dataSource = source
    }
}

class DataSource {
    
    
    var url: String? = null
}

class MapOptions {
    
    
    var centerPoint: Point? = null
    var zoom = 0.0
    var rotate = 0.0

    override fun toString(): String {
    
    
        return "中心点:$centerPoint,缩放:$zoom,旋转:$rotate"
    }

    data class Point(val lat: Double, val lng: Double) {
    
    
        override fun toString(): String {
    
    
            return "经纬度:($lat,$lng)"
        }
    }

    infix fun Point?.to(point: Point?) {
    
    
        centerPoint = point
    }

}


inline fun map(block: Map.() -> Unit): Map = Map().apply(block)

//inline fun layer(block: Layer.() -> Unit): Layer = Layer().apply(block)
inline fun dataSource(block: DataSource.() -> Unit): DataSource = DataSource().apply(block)
inline fun mapOptions(block: MapOptions.() -> Unit): MapOptions = MapOptions().apply(block)

inline fun Map.layer(block: Layer.() -> Unit): Layer {
    
    
    return Layer().apply {
    
    
        block()
        layerList.add(this)
    }
}

class ExtLayerListener : LayerListener {
    
    
    private var addBlock: (() -> Unit)? = null
    private var removeBlock: (() -> Unit)? = null
    private var errorBlock: (() -> Unit)? = null

    override fun onAdd() {
    
    
        addBlock?.let {
    
     it() }
    }

    fun onAdd(block: () -> Unit) {
    
    
        addBlock = block
    }

    override fun onRemove() {
    
    
        removeBlock?.let {
    
     it() }
    }

    fun onRemove(block: () -> Unit) {
    
    
        removeBlock = block
    }

    override fun onError() {
    
    
        errorBlock?.let {
    
     it() }
    }

    fun onError(block: () -> Unit) {
    
    
        errorBlock = block
    }
}

inline fun Map.listener(block: ExtLayerListener.() -> Unit) {
    
    
    layerListener = ExtLayerListener().apply {
    
    
        block()
    }
}

Общий эффект от создания класса карты карты DSL следующий:

map {
    
    
    mapOptions to mapOptions {
    
    
        centerPoint to MapOptions.Point(355.53, 66.66)
        zoom = 1.0
        rotate = 180.0
    }
    layer {
    
    
        layerName = "第一图层"
    } + dataSource {
    
    
        url = "https://baidu.com"
    }
    listener {
    
    
        onAdd {
    
    
            Log.e("layer", "新增图层")
        }
        onRemove {
    
    
            Log.e("layer", "移除图层")
        }
        onError {
    
    
            Log.e("layer", "图层错误")
        }
    }
}

4. Преимущества и недостатки DSL

На этом этапе каждый должен иметь возможность создать собственный простой DSL, так зачем же нам создавать DSL? Разве нельзя писать код напрямую, используя оригинальный синтаксис Kotlin? Конечно, раз уж DSL существует, то у него должны быть свои преимущества и недостатки.

Преимущества:
Общая структура кода DSL проста и понятна. По сравнению с традиционным методом написания, большой объем кода можно опустить, и он легко читается. Вот почему мы используем синтаксический сахар Kotlin для преобразования обычных фрагментов кода в DSL. Если часть нашей бизнес-логики необходимо широко использовать в одном проекте или повторно использовать в нескольких проектах, мы можем полностью инкапсулировать ее в DSL, который легко писать и легко читать.

Недостатки:
Нам нужно инкапсулировать это самостоятельно.Логика инкапсуляции сложна и ее легко разочаровать.В конце концов, логику кода DSL можно полностью реализовать с помощью обычного написания кода на Kotlin.
Для людей, отличных от автора, существует определенная стоимость ее чтения и использования.В конце концов, после завершения инкапсуляции писатель знает, как писать и использовать логику, в то время как для другого человека это требует определенного количества время понять и изучить, прежде чем использовать его.

Необходимость обучения:
многие проекты Kotlin с открытым исходным кодом теперь используют большое количество самоинкапсулируемых DSL. Если вы не понимаете некоторые принципы, вы будете сбиты с толку при чтении этих библиотек с открытым исходным кодом.

Guess you like

Origin blog.csdn.net/weixin_43864176/article/details/128594594