Kotlin DSL - 实用的BigDecimal计算

能够在获得一个Map<String, *>数据集对象,对其进行java.util.BigDecimal“安全地”复杂数值计算的DSL提供。

DSL设计:

// get records
val records: Map<String, *> = querySql()

// do calculation
val result = calculate(records) {
  (n("numerator") + n("numerator2")) / n("denominator")
}

还是存在一些语法噪音,如n(key: String),表示由一个key字符串,获得一个安全的BigDecimal对象。

对比直接使用Scala插值器num"key1"等的方式,我觉得还是差了一点。

# SafeBigDecimal

为了避免直接重载BigDecimal的操作符,定义了一个新的Wrapper对象SafeBigDecimal,用来包装BigDecimal后对其进行拓展。

class SafeBigDecimal(
    val value: BigDecimal? = null,
    private val mathContext: MathContext = MathContext.DECIMAL64
) : Number() {
    companion object {
        val NULL_SAFE_BIG_DECIMAL = SafeBigDecimal()
    }

    // --- kotlin ext
    override fun toDouble(): Double = value?.toDouble() ?: Double.MIN_VALUE
    override fun toFloat(): Float = value?.toFloat() ?: Float.MIN_VALUE
    override fun toLong(): Long = value?.toLong() ?: Long.MIN_VALUE
    override fun toInt(): Int = value?.toInt() ?: Int.MIN_VALUE
    override fun toChar(): Char = value?.toChar() ?: Char.MIN_VALUE
    override fun toShort(): Short = value?.toShort() ?: Short.MIN_VALUE
    override fun toByte(): Byte = value?.toByte() ?: Byte.MIN_VALUE

    // --- object

    override fun equals(other: Any?): Boolean =
        if (value == null && other == null) true
        else value?.equals(other) ?: false

    override fun hashCode(): Int = value.hashCode()

    override fun toString(): String = value.toString()
}

然后是运算符重载,当+-为null时候取0值,当*/的取null值,以及当/ 0的除零情况,包装为value == nullSafeBigDecimal对象用于允许继续计算。

  1. BigDecimal运算符重载

        operator fun plus(value: BigDecimal?): SafeBigDecimal =
            when {
                value == null -> this
                this.value == null -> value.toSafeBigDecimal()
                else -> SafeBigDecimal(this.value.add(value, mathContext), mathContext)
            }
    
        operator fun minus(value: BigDecimal?): SafeBigDecimal =
            when {
                value == null -> this
                this.value == null -> value.toSafeBigDecimal()
                else -> SafeBigDecimal(this.value.subtract(value, mathContext), mathContext)
            }
    
        operator fun div(value: BigDecimal?): SafeBigDecimal =
            if (this.value == null || value == null || value == ZERO) NULL_SAFE_BIG_DECIMAL
            else SafeBigDecimal(this.value.divide(value, mathContext), mathContext)
    
        operator fun times(value: BigDecimal?): SafeBigDecimal =
            if (this.value == null || value == null) NULL_SAFE_BIG_DECIMAL
            else SafeBigDecimal(this.value.multiply(value, mathContext), mathContext)
    
  2. Self运算符重载

        operator fun plus(value: SafeBigDecimal): SafeBigDecimal =
            this + value.value
    
        operator fun minus(value: SafeBigDecimal): SafeBigDecimal =
            this - value.value
    
        operator fun div(value: SafeBigDecimal): SafeBigDecimal =
            this / value.value
    
        operator fun times(value: SafeBigDecimal): SafeBigDecimal =
            this * value.value
    

# Any? to SafeBigDecimal

然后是为了支持从Map<String, *>对象取值的转换,定义了Any?.toSafeBigDecimal(): SafeBigDecimal的拓展函数。

fun Any?.toSafeBigDecimal(): SafeBigDecimal = when {
    this == null -> NULL_SAFE_BIG_DECIMAL
    this is SafeBigDecimal -> this
    this is BigDecimal -> SafeBigDecimal(this)
    this is BigInteger -> SafeBigDecimal(this.toBigDecimal())
    this is String -> this.toBigDecimal().toSafeBigDecimal()
    this is Double -> BigDecimal.valueOf(this).toSafeBigDecimal()
    this is Float -> BigDecimal.valueOf(this.toDouble()).toSafeBigDecimal()
    this is Long -> BigDecimal.valueOf(this).toSafeBigDecimal()
    this is Int -> BigDecimal.valueOf(this.toLong()).toSafeBigDecimal()
    this is Char -> BigDecimal.valueOf(this.code.toLong()).toSafeBigDecimal()
    this is Short -> BigDecimal.valueOf(this.toLong()).toSafeBigDecimal()
    this is Byte -> BigDecimal.valueOf(this.toLong()).toSafeBigDecimal()
    this is Byte -> BigDecimal.valueOf(this.toLong()).toSafeBigDecimal()
    else -> NULL_SAFE_BIG_DECIMAL
}

这样就可以通过任何类型map[key]: Any?安全地获得一个可运算的SafeBigDecimal对象

# Calculate DSL

最后是最一开始的DSL设计的实现。

首先实现n取值函数,用来对字符串key调用map::gettoSafeBigDecimal取值。

fun Map<String, *>.n(key: String): SafeBigDecimal = this[key]?.toSafeBigDecimal() ?: NULL_SAFE_BIG_DECIMAL

然后是DSL的calculate方法实现。暂时忽略MathContext,先做一个简单版。

现在知道我们需要实现一个入参为map: Map<String, *>, fomula: Map<String, *>.() -> R的方法,具体实现非常简单,仅仅对map运行传入的第二个formulalambda函数,返回formula的运算结果。

inline fun <reified T> calculate(map: Map<String, *>, formula: Map<String, *>.() -> T): T =
    map.run(formula)

# Jackson Serialization

为了保证序列化和反序列化,分别在Jackson编写自定义的StdSerializerStdDeserializer

# SafeBigDecimalJsonSerializer

class SafeBigDecimalJsonSerializer : StdSerializer<SafeBigDecimal>(SafeBigDecimal::class.java) {
    override fun serialize(value: SafeBigDecimal, gen: JsonGenerator, serializers: SerializerProvider) =
        gen.writeNumber(value.value)
}

# SafeBigDecimalJsonDeserializer

class SafeBigDecimalJsonDeserializer : StdDeserializer<SafeBigDecimal>(SafeBigDecimal::class.java) {
    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SafeBigDecimal =
        p.readValueAs(BigDecimal::class.java).toSafeBigDecimal()
}

# 测试

编写测试代码

fun main() {
    val map = mapOf(
        "key1" to "1",
        "key2" to 0,
        "key3" to 0
    )

    calculate(map) {
        (n("key1") + n("key2")) / n("key3")
    }.also { println("(key1 + key2) / key3 = $it") }

    calculate(map) {
        (n("key1") + n("key2")) / n("key1")
    }.also { println("(key1 + key2) / key1 = $it") }
}

结果如下:

(key1 + key2) / key3 = null
(key1 + key2) / key1 = 1

Process finished with exit code 0