基于 Kotlin 特性实现的验证框架

简介: 基于 Kotlin 特性实现的验证框架

一. kvalidation 介绍



kvalidation 地址:https://github.com/fengzhizi715/kvalidation

它包含如下的功能:


  • DSL 风格
  • 支持对象的验证
  • 内含多个验证规则,也支持自定义验证规则
  • 支持对象中属性的验证
  • 支持 RxJava


二. kvalidation 设计



2.1 类的验证


首先,定义一个 ValidateRule  的范型接口并使用逆变,它表示类的验证规则。


可以查看之前的文章:《Kotlin 范型之协变、逆变》 了解逆变。


ValidateRule 包含了两个方法:

interface ValidateRule<in T> {
    fun validate(data: T): Boolean
    fun errorMessage(): String
}


然后,定义一个用于验证类的 Validator,它继承自 LinkedHashSet。可以将 ValidateRule 通过 addRule() 添加到 Validator,另外 addRule() 还是使用了infix修饰。


真正的类的验证是在 validate() 进行的,当所有的 ValidateRule 都通过时,才算真正的验证通过。任何一个 ValidateRule 验证失败,都会导致类的验证失败。

open class Validator<T> : LinkedHashSet<ValidateRule<T>>() {
    fun validate(data: T,
                      onSuccess: (() -> Unit)? = null,
                      onError: ((String) -> Unit)? = null): Boolean {
        forEach {
            if (!it.validate(data)) {
                onError?.invoke(it.errorMessage())
                return false
            }
        }
        onSuccess?.invoke()
        return true
    }
    infix fun addRule(rule: ValidateRule<T>): Validator<T> {
        add(rule)
        return this
    }
}


2.2 属性的验证


属性的验证是通过 PropertyValidator 类实现的,和之前的 Validator 无关。


PropertyValidator 的主要方法包括 mustBe()、field()、fields()。


类的属性通过 field() 方法进行判断,多个属性可以通过 fields() 方法进行判断,而 mustBe() 方法可以不限定任何属性。

class PropertyValidator<T> (
    private val validationProcessItems: MutableList<ValidationProcessItem<T>> = mutableListOf(),
    private val fieldNames: List<String> = emptyList()) {
    fun mustBe(specName: String = "", validateFunction: T.() -> Boolean): ValidationSpec<T> {
        val spec = ValidationSpec(specName = specName, validateFunction = validateFunction, fieldNames = fieldNames)
        validationProcessItems.add(spec)
        return spec
    }
    fun field(fieldName: String, block: PropertyValidator<T>.() -> Unit) {
        val fieldValidator = PropertyValidator(validationProcessItems, listOf(fieldName))
        block.invoke(fieldValidator)
    }
    fun fields(vararg fieldNames: String, block: PropertyValidator<T>.() -> Unit) {
        val fieldValidator = PropertyValidator(validationProcessItems, fieldNames.toList())
        block.invoke(fieldValidator)
    }
    ......
}


类的属性也有类似的 ValidateRule。它是 ValidationSpec ,用于验证属性的某一条规则。

open class ValidationSpec<T>(specName: String = "",
                             fieldNames: List<String>,
                             val validateFunction: T.() -> Boolean) : ValidationProcessItem<T>(specName, fieldNames) {
    private var messageFunction: ((T) -> String)? = null
    fun errorMessage(messageFunction: T.() -> String) {
        this.messageFunction = messageFunction
    }
    fun showMessage(target: T) = messageFunction?.invoke(target) ?: "validation failed"
    fun isValid(target: T): Boolean = validateFunction(target)
}


ValidationSpec  的 isValid() 方法用于真正的验证,如果它返回 false 则表示该属性不满足当前的规则。


三. kvalidation 使用



3.1 使用 Validator


由于定义了一个 defineValidator()

fun <T> defineValidator(block: Validator<T>.() -> Unit): Validator<T> {
    val v = Validator<T>()
    block.invoke(v)
    return v
}


因此,定义一个 Validator 很简单,可以在 block 中添加 ValidateRule。

val validator = defineValidator<String>{
        this addRule EmailRule()
    }
    val email = "fengzhizi715@126.com"
    val result = validator.validate(email,onError = { println(it)})
    println(result)


3.2 Validator 中添加多个校验规则


由于 Validator 是一个 LinkedHashSet,因此可以在 block 中添加多个 ValidateRule。


例如下面的密码校验,使用了两个 ValidateRule:

val validator = defineValidator<String>{
        this addRule MinLengthRule(6)                                       // 密码长度不能小于6位
        this addRule PatternRule("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+$") // 密码必须数字和字母的组合
    }
    val password = "123456a"
    val result = validator.validate(password,onError = { println(it)})
    println(result)


3.3 支持 RxJava 的使用


由于定义了一个 RxValidator

class RxValidator<T>(private val data: T) : Validator<T>() {
    fun toObservable(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
        Observable.just(data)
            .map {
                validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
                })
            }
    fun toFlowable(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
        Flowable.just(data)
            .map {
                validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
                })
            }
    fun toSingle(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
        Single.just(data)
            .map {
                validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
                })
            }
    fun toMaybe(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
        Maybe.just(data)
            .map {
                validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
                })
            }
}


并且定义了一个 defineRxValidator() 和扩展函数 rxValidator()

fun <T> defineRxValidator(data: T, block: RxValidator<T>.() -> Unit): RxValidator<T> {
    val v = RxValidator<T>(data)
    block.invoke(v)
    return v
}
fun <T> T.rxValidator(block: RxValidator<T>.() -> Unit): RxValidator<T> {
    val v = RxValidator<T>(this)
    block.invoke(v)
    return v
}


因此 RxJava 的结合使用变得很简单,下面分别使用两种方式展示了如何结合 RxJava 的使用:

val email = "fengzhizi715@126.com"
    defineRxValidator(email){ this addRule EmailRule() }
        .toObservable( error = { println(it)})
        .subscribe{ println(it) }
    val invalidEmail = "fengzhizi715@126"
    invalidEmail.rxValidator { this addRule EmailRule() }
        .toObservable( error = { println(it)})
        .subscribe{ println(it) }


3.4 支持对象中属性的校验


参考上面的代码,在 kvalidation 中也事先定义了一个 definePropertyValidator()

fun <T> definePropertyValidator(block: PropertyValidator<T>.() -> Unit): PropertyValidator<T> {
    val v = PropertyValidator<T>()
    block.invoke(v)
    return v
}


因此,在定义一个 PropertyValidator 时,也可以在 block 中添加多个 mustBe()、field()、fields() 方法。

在 field()、fields() 中,还可以添加多个 mustBe() 方法

data class User(val name: String = "tony",val password: String = "abcdefg", val confirmPassword: String = "abcdefg" ,val email:String = "abc#abc.com")
val propertyValidator = definePropertyValidator<User> {
    mustBe { name.isNotBlank() }
    field("password") {
        mustBe("password not blank") { password.isNotBlank() }
        mustBe("password length range") { password.length in 6..20 }
    }
    fields("password", "confirmPassword") {
        mustBe("password confirmPassword same") { password == confirmPassword }
    }
    field("email") {
        mustBe("verify email") {
            email.validate{
                this addRule EmailRule()
            }
        }.errorMessage { "invalid email address" }
    }
}
fun main() {
    val user = User()
    val result = propertyValidator.validateAll(user)
    println(result)
    println(propertyValidator.validate(user))
}


在 email 字段中,mustBe() 里使用了

email.validate{
                this addRule EmailRule()
            }


它是一个扩展函数:

fun <T> T.validate(block: Validator<T>.() -> Unit): Boolean {
    val v = Validator<T>()
    block.invoke(v)
    return v.validate(this)
}


它实际上是调用了类的验证,并添加了 EmailRule。


四. 总结



kvalidation 是一个基于 Kotlin 特性的验证框架,这些特性包括范型、DSL、扩展函数、带接收者的函数类型等等。因此,它使用起来简洁,也有具有很好的可读性。

相关文章
|
Kotlin
Kotlin | 实现数据类(data)深拷贝
在Kotlin中,data数据类默认的copy方法实现的是浅拷贝,但我们有时候需要实现深拷贝。 在kotlin中,实现就比较容易了。
767 0
Kotlin | 实现数据类(data)深拷贝
|
4月前
|
编译器 Android开发 开发者
Android经典实战之Kotlin 2.0 迁移指南:全方位优化与新特性解析
本文首发于公众号“AntDream”。Kotlin 2.0 已经到来,带来了 K2 编译器、多平台项目支持、智能转换等重大改进。本文提供全面迁移指南,涵盖编译器升级、多平台配置、Jetpack Compose 整合、性能优化等多个方面,帮助开发者顺利过渡到 Kotlin 2.0,开启高效开发新时代。
193 0
|
6月前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
6月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
6月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
6月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
7月前
|
安全 Java 编译器
Kotlin 1.6 正式发布,带来哪些新特性?
Kotlin 1.6 正式发布,带来哪些新特性?
54 0
|
IDE Java 开发工具
Kotlin 1.8.0 现已发布,有那些新特性?
Kotlin 1.8.0 现已发布,有那些新特性?
205 0
Kotlin 1.8.0 现已发布,有那些新特性?
|
Java Maven Kotlin
java:Javalin一个简单而现代的Java和Kotlin web框架
java:Javalin一个简单而现代的Java和Kotlin web框架
391 0
java:Javalin一个简单而现代的Java和Kotlin web框架
|
设计模式 Kotlin
Kotlin设计模式实现之装饰者模式(Decorator)
装饰者模式(Decorator):在不改变对象自身的基础上,动态地给一个对象添加一些额外的职责。与继承相比,装饰者是一种更轻便灵活的做法。若要扩展功能,装饰者提供了比继承更有弹性的替代方法。
197 0
Kotlin设计模式实现之装饰者模式(Decorator)
下一篇
DataWorks