Kotlin DSL с необязательными полями

В настоящее время я изучаю Kotlin DSL.

Я играл с ним некоторое время, но я не могу решить свой вариант использования. У меня простой DSL, и меня не особо интересуют его типы, если я могу добиться такого синтаксиса:

    private fun getObj(): SET {
        return SET {
            ITEM {
                A = null
                B = "Hello world"
                C
                // D - exists in DSL but omitted here
            }
        }
    }

В фоновом режиме я теперь хочу различать определенные значения, установленные в блоке ITEM. B легко, это просто значение, но становится сложно для A и C. Почему-то я не могу различить null и no value набор. В настоящее время мой конструктор выглядит так, но я готов изменить его для достижения синтаксиса, указанного выше:

class ITEMBuilder {
    var A: String? = null
    var B: String? = null
    var C: String? = null
    var D: String? = null

    fun build() = ITEM(
        ItemValue(A),
        ItemValue(B),
        ItemValue(C),
        ItemValue(D)
    )
}

class ItemValue(val include: Boolean? = false, val value: String? = null) {
    constructor(value: String? = null): this(null != value, value)
}

Когда у меня есть последний объект, я хочу иметь возможность указать 4 разных этапа для каждого поля в ITEM:

  • набор значений
  • нулевой набор
  • значение не установлено
  • поле пропущено

Я пробовал разные типы, но мне не повезло, так как большинство вещей влияет на синтаксис. Я также попытался изменить геттеры / сеттеры в построителе, чтобы, возможно, поймать там обновление и иметь дополнительное внутреннее свойство, которое обновляется, но ни get, ни set не вызываются для значения null / no. Также пытался изменить поля на функции, но тогда у меня есть уродливые скобки () в синтаксисе DSL.

Было бы здорово, если бы кто-нибудь помог мне разобраться в этом.

Заранее спасибо!


person Stefan    schedule 05.05.2020    source источник


Ответы (1)


Для этого можно использовать приемники. Вот пример с одним параметром (в данном случае поле)

//Use like this
val item = ITEM {
        A = "Yay"
        B //Not omitted, but also not set
        //C can be omitted
        D = null
    }

Это эквивалентно

Item(//Included and set to "Yay"
     a=ItemValue(include=true, hasBeenSet=true, value="Yay"), 
     //Included, but not yet set
     b=ItemValue(include=true, hasBeenSet=false, value=null), 
     //Not included, and so not yet set
     c=ItemValue(include=false, hasBeenSet=false, value=null), 
     //Included, and set to null (Same as A)
     d=ItemValue(include=true, hasBeenSet=true, value=null))

Вы можете сделать это с помощью дополнительных полей типа String? и переопределить их установщики, чтобы изменить фактические поля типа ItemValue. Я включил свойство hasBeenSet в ItemValue, чтобы показать, установлено оно или нет.

Чтобы пометить свойства как включенные без их установки, вы можете переопределить методы получения, чтобы они изменили фактические поля, чтобы сделать их ItemValue(true, false), что означает, что они включены, но не установлены.

class Builder {
    var A: String? = null
        set(value) {
            a = ItemValue(true, true, value)
        }
        get() {
            a = ItemValue(true, false)
            return field
        }
    var B: String? = null
        set(value) {
            b = ItemValue(true, true, value)
        }
        get() {
            b = ItemValue(true, false)
            return field
        }
    var C: String? = null
        set(value) {
            c = ItemValue(true, true, value)
        }
        get() {
            c = ItemValue(true, false)
            return field
        }
    var D: String? = null
        set(value) {
            d = ItemValue(true, true, value)
        }
        get() {
            d = ItemValue(true, false)
            return field
        }

    var a: ItemValue = ItemValue(false, false)
    var b: ItemValue = ItemValue(false, false)
    var c: ItemValue = ItemValue(false, false)
    var d: ItemValue = ItemValue(false, false)

    fun build(): Item {
        return Item(a, b, c, d)
    }
}

fun ITEM(setters: Builder.() -> Unit): Item {
    val builder = Builder()
    builder.setters()
    return builder.build()
}

data class Item(val a: ItemValue, val b: ItemValue, val c: ItemValue, val d: ItemValue)
data class ItemValue(val include: Boolean, val hasBeenSet: Boolean, val value: String? = null)

Это выглядит хорошо и решает пропущенный случай. Однако это не решение для //Use like this val item = ITEM { A = "Yay" B //Not omitted, but also not set //C can be omitted D = null } и

Item(//Included and set to "Yay"
     a=ItemValue(include=true, hasBeenSet=true, value="Yay"), 
     //Included, but not yet set
     b=ItemValue(include=true, hasBeenSet=false, value=null), 
     //Not included, and so not yet set
     c=ItemValue(include=false, hasBeenSet=false, value=null), 
     //Included, and set to null (Same as A)
     d=ItemValue(include=true, hasBeenSet=true, value=null))
, нулевого значения и отсутствия значения. Желаемым результатом будет то, что класс ItemValue будет показывать String? для всех случаев, кроме пропущенного. Если подумать об этом подробнее, мне определенно придется расширить класс ItemValue, желаемый результат объекта будет выглядеть примерно так: hasBeenSet, а затем варианты использования будут выглядеть так: ItemValue ItemValue(true, false)
class Builder {
    var A: String? = null
        set(value) {
            a = ItemValue(true, true, value)
        }
        get() {
            a = ItemValue(true, false)
            return field
        }
    var B: String? = null
        set(value) {
            b = ItemValue(true, true, value)
        }
        get() {
            b = ItemValue(true, false)
            return field
        }
    var C: String? = null
        set(value) {
            c = ItemValue(true, true, value)
        }
        get() {
            c = ItemValue(true, false)
            return field
        }
    var D: String? = null
        set(value) {
            d = ItemValue(true, true, value)
        }
        get() {
            d = ItemValue(true, false)
            return field
        }

    var a: ItemValue = ItemValue(false, false)
    var b: ItemValue = ItemValue(false, false)
    var c: ItemValue = ItemValue(false, false)
    var d: ItemValue = ItemValue(false, false)

    fun build(): Item {
        return Item(a, b, c, d)
    }
}

fun ITEM(setters: Builder.() -> Unit): Item {
    val builder = Builder()
    builder.setters()
    return builder.build()
}

data class Item(val a: ItemValue, val b: ItemValue, val c: ItemValue, val d: ItemValue)
data class ItemValue(val include: Boolean, val hasBeenSet: Boolean, val value: String? = null)
_9_

person user    schedule 05.05.2020
comment
Ваш обновленный код охватывает случай для нулевых значений, однако он не может различать пропущенные значения и значения, которые не установлены, B, C, include=true, ItemValue - person Stefan; 05.05.2020
comment
Да, это решило проблему. Спасибо за помощь. Позвольте мне прочитать код, чтобы понять, как это работает. - person Stefan; 05.05.2020
comment
Вот ссылка на Котлин площадка. Вы можете запустить его и увидеть результат самостоятельно. - person Stefan; 06.05.2020