2

我有一个具有一些可为空属性的类

data class RequestModel(
    val description: String?
)

和验证功能

fun validate(model: RequestModel): RequestModel{
    if(model.description == null) throw IllegalArgumentException("description must be non null")
    return model
}

在此验证步骤之后,我需要一种方法来指示属性的不可为空description性。

一种解决方案是创建一个具有非空属性的新数据类data class RequestModel(val description: String)
但我正在寻找一种通用方法来避免为每个用例创建新类。

理想的通用解决方案:

fun validate(model: RequestModel): NoNullableField<RequestModel>

如何以通用方式从具有可空属性的类的属性中删除可空性?使用某种 kotlin 编译器合约有用吗?

4

2 回答 2

1

您可以使用Kotlin 反射来获取所有属性并检查它们是否不为空:

inline fun <reified T : Any> T.requireNoNullableProperties() = NoNullableProperties(this, T::class)

class NoNullableProperties<out T : Any>(val obj: T, clazz: KClass<T>) {
    init {
        clazz.memberProperties.forEach { prop ->
            if (prop.returnType.isMarkedNullable) {
                prop.isAccessible = true
                requireNotNull(prop.get(obj)) {
                    "${prop.name} must be not null, obj - [$obj]"
                }
            }
        }
    }

    operator fun <R> get(property: KProperty1<in T, R?>): R = requireNotNull(property.get(obj)) {
        "Extension and mutable properties can't be validated, property - [$property], obj - [$obj]"
    }
}

用例:

val validated = model.requireNoNullableProperties()
val description: String = validated[RequestModel::description]

此外,您可以提取validated[RequestModel::description]到扩展属性NoNullableProperties<RequestModel>

val ValidRequestModel.description get() = get(RequestModel::description)

在哪里ValidRequestModel

typealias ValidRequestModel = NoNullableProperties<RequestModel>

用例:

val validated = model.requireNoNullableProperties()
val description: String = validated.description
于 2019-11-24T15:16:27.143 回答
1

首先,如果你想使用抽象的可验证对象,你需要Validatable接口:

interface Validatable {
    fun validate()
}

您还需要一个代表经过验证的对象的类:

data class Valid<out T : Validatable>(val obj: T) {
    init {
        obj.validate()
    }

    fun <U : Any> U?.mustBeValidated(): U = checkNotNull(this) {
        "${obj::class.jvmName}.validate() successfully validated invalid object $obj"
    }
}

现在您需要Validatable.valid()有助于创建Valid实例的函数:

fun <T : Validatable> T.valid(): Valid<T> = Valid(this)

以下是您如何成为自己的RequestModelValidatable

data class RequestModel(
    val description: String?
) : Validatable {
    override fun validate() {
        requireNotNull(description) { "description must be non null" }
    }
}

val Valid<RequestModel>.description get() = obj.description.mustBeValidated()

以下是如何使用它:

val validModel: Valid<RequestModel> = model.valid()
val notNullDescription: String = validModel.description

您还可以使Validinline。由于内联类不能有init块,init逻辑被移到工厂方法。并且由于内联类主构造函数应该是public,构造函数被标记为@Experimental private annotation class ValidInternal防止非法构造函数使用:

@UseExperimental(ValidInternal::class)
fun <T : Validatable> T.valid(): Valid<T> {
    validate()
    return Valid(this)
}

@Experimental
private annotation class ValidInternal

inline class Valid<out T : Validatable> @ValidInternal constructor(
    // Validatable is used here instead of T
    // because inline class cannot have generic value parameter
    private val _obj: Validatable
) {
    @Suppress("UNCHECKED_CAST") // _obj is supposed to be T
    val obj: T
        get() = _obj as T

    fun <U : Any> U?.mustBeValidated(): U = checkNotNull(this) {
        "${obj::class}.validate() successfully validated invalid object $obj"
    }
}
于 2019-11-25T17:44:09.383 回答