使用没有设置器的委托创建变量

时间:2019-10-16 12:37:14

标签: kotlin delegates immutability mutability

我正在尝试使用不提供setValue(...)方法的委托来创建委托var属性。换句话说,我需要一个可以重新分配的属性,但是只要没有被重新分配,就应该通过委托获取它的值。

我正在使用xenomachina CLI arguments parser库,该库使用委托。只要我具有val属性,此方法就可以很好地工作。在某些情况下,我需要能够在运行时动态更改这些属性,但是需要可变的var。我不能在这里简单地使用var,因为该库在负责参数解析的委托中没有提供setValue(...)方法。

理想情况下,我想要这样的东西:

class Foo(parser: ArgParser) {
    var myParameter by parser.flagging(
        "--my-param",
        help = "helptext"
    )
}

由于缺少设置器而无法使用。

到目前为止,我已经尝试使用setter扩展功能扩展Delegate类,但是在内部它也使用val,所以我无法更改它。我尝试将委托包装到另一个委托中,但是当我这样做时,库将无法识别我包装的选项。虽然我可能在那里错过了一些东西。 我不能按如下所示将值重新分配给新的变量:

private val _myParameter by parser.flagging(...)
var myParameter = _myParameter

,因为这似乎使解析器感到困惑,并且在访问第一个委托属性后立即停止评估其余参数。此外,它不是特别漂亮。

如何结合使用不包含setter和var属性的委托?

2 个答案:

答案 0 :(得分:0)

在这里,您可以包装ReadOnlyProperty以使其按您希望的方式工作:

class MutableProperty<in R, T>(
    // `(R, KProperty<*>) -> T` is accepted here instead of `ReadOnlyProperty<R, T>`,
    // to enable wrapping of properties which are based on extension function and don't
    // implement `ReadOnlyProperty<R, T>`
    wrapped: (R, KProperty<*>) -> T
) : ReadWriteProperty<R, T> {
    private var wrapped: ((R, KProperty<*>) -> T)? = wrapped // null when field is assigned
    private var field: T? = null

    @Suppress("UNCHECKED_CAST") // field is T if wrapped is null
    override fun getValue(thisRef: R, property: KProperty<*>) =
        if (wrapped == null) field as T
        else wrapped!!(thisRef, property)

    override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
        field = value
        wrapped = null
    }
}

fun <R, T> ReadOnlyProperty<R, T>.toMutableProperty() = MutableProperty(this::getValue)

fun <R, T> ((R, KProperty<*>) -> T).toMutableProperty() = MutableProperty(this)

用例:

var lazyVar by lazy { 1 }::getValue.toMutableProperty()

这是包装属性委托提供者的方法:

class MutableProvider<in R, T>(
    private val provider: (R, KProperty<*>) -> (R, KProperty<*>) -> T
) {
    operator fun provideDelegate(thisRef: R, prop: KProperty<*>): MutableProperty<R, T> =
        provider(thisRef, prop).toMutableProperty()
}

fun <T> ArgParser.Delegate<T>.toMutableProvider() = MutableProvider { thisRef: Any?, prop ->
    provideDelegate(thisRef, prop)::getValue
}

用例:

var flagging by parser.flagging(
    "--my-param",
    help = "helptext"
).toMutableProvider()

答案 1 :(得分:0)

您可以用以下类包装委托:

class DefaultDelegate<T>(private val default: Delegate<T>){
    private var _value: T? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = 
        _value?: default.value

    operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
        _value = value
    }
}

用法:

class Foo(parser: ArgParser) {
    var myParameter: Boolean? by DefaultDelegate(parser.flagging(
        "--my-param",
        help = "helptext"
    ))
}

如果您需要为空性:

class DefaultDelegate<T>(private val default: Delegate<T>){
    private var modified = false
    private var _value: T? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = 
        if (modified) _value else default.value

    operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
        _value = value
        modified = true
    }
}