美文网首页
从Dart到Kotlin扩展函数的基本常规操作及应用场景

从Dart到Kotlin扩展函数的基本常规操作及应用场景

作者: BlueSocks | 来源:发表于2023-12-06 17:44 被阅读0次

    Kotlin的扩展属性与扩展方法及其实战应用

    前言

    扩展函数真的是现代开发语言的神器,不管是之前用的 Kotlin 还是 Dart 都可以使用扩展函数,在不改变原本类的前提下添加一些方法和变量,特别的方便。

    前段时间在写 Flutter 项目,定义了很多扩展函数,有各种定义:

    image.png

    当时还不觉得,最近又开始写 Kotlin 项目突然觉得,好像自己一直都是用 Kotlin 的扩展方法,不带括号的写法自己还真没怎么用,一时间反应不过来赶紧理清一下头绪,属实是被 Dart 给教育了一波。

    关于 KT 的扩展大家可能都知道可以扩展对象的方法,也能扩展对象的属性,如果从一个简单的外观来区分,我们简单的区分为带括号的和不带括号的(扩展方法和扩展属性)。

    下面一起复习一下 KT 的扩展函数的扩展方法和扩展属性吧。

    一、跟着Dart学Kotlin扩展函数

    在 Dart 中我们可以定义扩展方法 dp() 并且可以传递参数。同时我们也可以定义 ap 扩展属性,给 int 加入一个属性,并且我们指定了这个属性是只读的,代码如下:

    extension IntExtension on int {
      double get ap {
        return ScreenUtil.getInstance().getAdapterSize(toDouble());
      }
    
      double dp() {
        return ScreenUtil.getInstance().getAdapterSize(this);
      }
    }
    
    

    他们的功能是一样的,但是使用起来就不同了,走的逻辑分支也不同,一个是调用方法,一个是调用属性。

    image.png

    其实我们扩展属性也是可以分别设置读写设置的:

    extension IdNameEntityExtension on IdNameEntity { String get propertyId { return id ?? ""; }

    set propertyId(String id) { this.id = id; }

    String get propertyName { return name!; }

    set propertyName(String value) { name = value; } }

    比如我们对一个 IDName 的实体做一个扩展属性,那么可以设置它的可读可写,虽然是一个简单的例子看起来很傻,但是这个简单的示例确实最基本的属性扩展可读写的权限控制演示。

    同样的代码我们也可以用 Kotlin 实现:

    val Int.ap: Int
        get() = CommUtils.dip2px(this)
    
    fun Int.sp(): Int {
        return CommUtils.dip2px(this)
    }
    
    

    同样的设置 sp() 为扩展方法,ap 为可读的扩展属性。只是与 Dart 的扩展不同的是标识不同,扩展函数用 fun 标识函数,扩展属性用 val 或 var。

    对应自定义对象的扩展, KT 也是类似的做法:

    var IDNameEntity.propertyId: String
        get() = this.id
        set(value:String) {
            this.id = value
        }
    
    val IDNameEntity.propertyName :String
           get() = this.name
    
    

    如果只读的,我们用下面的方法 val 标识函数,使用 get 返回对应的值,如果是可读可写的就需要用 var 标识并且设置 get 和 set 标识并处理对应的逻辑。

    测试代码:

      val idNameEntity = IDNameEntity("25", "关羽")
      val propertyName = idNameEntity.propertyName
      idNameEntity.propertyId = "30"
      val propertyId = idNameEntity.propertyId
    
      YYLogUtils.w("propertyName:$propertyName propertyId:$propertyId")
    
    

    打印:

    image.png

    二、实践一:空间大小格式化

    熟悉了扩展方法和扩展属性,我们就可以先来一个简单的空间大小格式化的处理。

    一般我们获取到当前 App 占用的空间之后,我们就会把内置卡占用空间和外置卡占用空间加起来就是 App 占用空间。

    如果单位的格式不同我们就需要转换为最基本的 B 单位相加,然后再处理格式化到指定的空间展示,我们可以把这一过程用扩展方法和扩展属性来集中管理。

    我们先定义一个普通的类,用于传入基本的 B 单位:

    class DataUnitSize constructor(val rawBytes: Long) {
    
        fun toDouble(unit: DataUnit): Double = convertDataUnit(rawBytes.toDouble(), DataUnit.BYTES, unit)
    
        fun toLong(unit: DataUnit): Long = convertDataUnit(rawBytes, DataUnit.BYTES, unit)
    
        //默认的 toString 输出最基本的 B 单位
        override fun toString(): String = String.format("%dB", rawBytes)
    
        fun toString(unit: DataUnit, decimals: Int = 2): String {
            require(decimals >= 0) { "小数点必须大于0, 现在的小数点是$decimals" }
            val number = toDouble(unit)
            if (number.isInfinite()) {
                return number.toString()
            }
    
            val newDecimals = decimals.coerceAtMost(12)
            return DecimalFormat("0").run {
                if (newDecimals > 0) minimumFractionDigits = newDecimals
                roundingMode = RoundingMode.HALF_UP
                format(number) + unit.shortName
            }
        }
    }
    
    

    我们这里简单的定义一个构造方法,用于传递最基本的 B 单位,重写和提供 toString 方法,分别打印最基本的 B 单位大小,和指定格式的大小。

    内部涉及到 DataUnit 单位枚举 和 convertDataUnit 单位转换的方法:

    enum class DataUnit(val shortName: String) {
        BYTES("B"),
        KILOBYTES("KB"),
        MEGABYTES("MB"),
        GIGABYTES("GB"),
        TERABYTES("TB"),
    }
    
    private const val BYTES_PER_KB: Long = 1024  //KB
    private const val BYTES_PER_MB = BYTES_PER_KB * 1024  //MB
    private const val BYTES_PER_GB = BYTES_PER_MB * 1024  //GB
    private const val BYTES_PER_TB = BYTES_PER_GB * 1024  //TB
    
    //主要的单位转换方法之一
    fun convertDataUnit(value: Long, sourceUnit: DataUnit, targetUnit: DataUnit): Long {
        val valueInBytes = when (sourceUnit) {
            DataUnit.BYTES -> value
            DataUnit.KILOBYTES -> Math.multiplyExact(value, BYTES_PER_KB)
            DataUnit.MEGABYTES -> Math.multiplyExact(value, BYTES_PER_MB)
            DataUnit.GIGABYTES -> Math.multiplyExact(value, BYTES_PER_GB)
            DataUnit.TERABYTES -> Math.multiplyExact(value, BYTES_PER_TB)
        }
        return when (targetUnit) {
            DataUnit.BYTES -> valueInBytes
            DataUnit.KILOBYTES -> valueInBytes / BYTES_PER_KB
            DataUnit.MEGABYTES -> valueInBytes / BYTES_PER_MB
            DataUnit.GIGABYTES -> valueInBytes / BYTES_PER_GB
            DataUnit.TERABYTES -> valueInBytes / BYTES_PER_TB
        }
    }
    
    //主要的单位转换方法之一
    fun convertDataUnit(value: Double, sourceUnit: DataUnit, targetUnit: DataUnit): Double {
        val valueInBytes = when (sourceUnit) {
            DataUnit.BYTES -> value
            DataUnit.KILOBYTES -> value * BYTES_PER_KB
            DataUnit.MEGABYTES -> value * BYTES_PER_MB
            DataUnit.GIGABYTES -> value * BYTES_PER_GB
            DataUnit.TERABYTES -> value * BYTES_PER_TB
        }
        require(!valueInBytes.isNaN()) { "DataUnit value cannot be NaN." }
        return when (targetUnit) {
            DataUnit.BYTES -> valueInBytes
            DataUnit.KILOBYTES -> valueInBytes / BYTES_PER_KB
            DataUnit.MEGABYTES -> valueInBytes / BYTES_PER_MB
            DataUnit.GIGABYTES -> valueInBytes / BYTES_PER_GB
            DataUnit.TERABYTES -> valueInBytes / BYTES_PER_TB
        }
    }
    
    
    

    有了基本的类处理,和基本的单位转换方法,我们就能定义相对于的扩展方法和扩展属性了:

    //定义常用的一系列扩展属性(只读)
    val Long.b get() = this.toDataSize(DataUnit.BYTES)
    val Long.kb get() = this.toDataSize(DataUnit.KILOBYTES)
    val Long.mb get() = this.toDataSize(DataUnit.MEGABYTES)
    val Long.gb get() = this.toDataSize(DataUnit.GIGABYTES)
    val Long.tb get() = this.toDataSize(DataUnit.TERABYTES)
    
    val Int.b get() = this.toLong().toDataSize(DataUnit.BYTES)
    val Int.kb get() = this.toLong().toDataSize(DataUnit.KILOBYTES)
    val Int.mb get() = this.toLong().toDataSize(DataUnit.MEGABYTES)
    val Int.gb get() = this.toLong().toDataSize(DataUnit.GIGABYTES)
    val Int.tb get() = this.toLong().toDataSize(DataUnit.TERABYTES)
    
    val Double.b get() = this.toDataSize(DataUnit.BYTES)
    val Double.kb get() = this.toDataSize(DataUnit.KILOBYTES)
    val Double.mb get() = this.toDataSize(DataUnit.MEGABYTES)
    val Double.gb get() = this.toDataSize(DataUnit.GIGABYTES)
    val Double.tb get() = this.toDataSize(DataUnit.TERABYTES)
    
    //定义的扩展方法之一,转换为 new 出来的 DataSize 对象
    fun Long.toDataSize(unit: DataUnit): DataUnitSize {
        return DataUnitSize(convertDataUnit(this, unit, DataUnit.BYTES))
    }
    
    //定义的扩展方法之一,转换为 new 出来的 DataSize 对象
    fun Double.toDataSize(unit: DataUnit): DataUnitSize {
        return DataUnitSize(convertDataUnit(this, unit, DataUnit.BYTES).roundToLong())
    }
    
    // 给自定义的 DataSize 定义扩展方法,可以自动格式化到指定的单位
    fun DataUnitSize.autoFormatDataSize(): String {
        val dataSize = this
        return when {
            dataSize.inWholeTerabytes >= 1 -> dataSize.toString(DataUnit.TERABYTES)
            dataSize.inWholeGigabytes >= 1 -> dataSize.toString(DataUnit.GIGABYTES)
            dataSize.inWholeMegabytes >= 1 -> dataSize.toString(DataUnit.MEGABYTES)
            else -> dataSize.toString(DataUnit.KILOBYTES)
        }
    }
    
    // 给自定义的 DataSize 定义扩展方法,是否满足当前单位
    val DataUnitSize.inWholeBytes: Long get() = toLong(DataUnit.BYTES)
    val DataUnitSize.inWholeKilobytes: Long get() = toLong(DataUnit.KILOBYTES)
    val DataUnitSize.inWholeMegabytes: Long get() = toLong(DataUnit.MEGABYTES)
    val DataUnitSize.inWholeGigabytes: Long get() = toLong(DataUnit.GIGABYTES)
    val DataUnitSize.inWholeTerabytes: Long get() = toLong(DataUnit.TERABYTES)
    
    

    使用的时候,可以手动指定格式化单位:

        val dataSize1 = 888.mb
    
        YYLogUtils.w("格式化 B:${dataSize1.toString()}")
        YYLogUtils.w("格式化 KB:${dataSize1.toString(DataUnit.KILOBYTES)}")
        YYLogUtils.w("格式化 GB:${dataSize1.toString(DataUnit.GIGABYTES)}")
    
    

    也可以自动转换格式化单位:

        val sizeInBytes = 1500000.b 
        val formattedSize = sizeInBytes.autoFormatDataSize()
        YYLogUtils.w(formattedSize) // 输出:1.43MB
    
    

    如果想要想加也可以自己实现想加,当然也可以自己实现 Comparable 接口加上 operator 操作符,有兴趣可以往下看。

        val dataSize1 = 888.mb
    
        val dataSize2 = 1.gb
    
        val plusSize = DataUnitSize(dataSize1.rawBytes + dataSize2.rawBytes).autoFormatDataSize()
        YYLogUtils.w(plusSize)
    
    

    打印 Log 如下:

    image.png

    二、实践二:汇率格式化

    如果我们的需求是,做完这个任务获得100美元,做完另一个任务获得80港币,最终我需要计算一共获得多少人民币。

    我们可以通过类似的方法来实现,由于上面给出了具体的实现步骤这里就直接上代码了:

    enum class CurrencyUnit(val prefix: String, val fullName: String, val exchangeRate: Double) {
        CNY("¥", "CNY", 1.0),
        USD("$", "USD", 8.0),
        SGD("S$", "SGD", 5.0),
        HKD("HK$", "HKD", 0.8),
    }
    
    //简单的转换方法
    fun convertCurrency(value: Double, sourceUnit: CurrencyUnit, targetUnit: CurrencyUnit): Double {
        val valueInRMB = value * sourceUnit.exchangeRate
        return valueInRMB / targetUnit.exchangeRate
    }
    
    /**
    使用 @JvmInline value class 优化旨在提高内存和性能效率
    使用 @JvmInline value class 必须有且仅有一个属性,主构造函数的参数只能是 val,
    并且类不能被继承,生成的字节码会在使用处直接嵌入,而不会引入额外的对象
    
    如果不想使用 @JvmInline value class 用普通的class也能行
     */
    @JvmInline
    value class CurrencyAmount constructor(private val rawAmount: Double) : Comparable<CurrencyAmount> {
    
        private fun toDouble(unit: CurrencyUnit): Double = convertCurrency(rawAmount, CurrencyUnit.CNY, unit)
    
        operator fun unaryMinus(): CurrencyAmount {
            return CurrencyAmount(-this.rawAmount)
        }
    
        operator fun plus(other: CurrencyAmount): CurrencyAmount {
            return CurrencyAmount(this.rawAmount + other.rawAmount)
        }
    
        operator fun minus(other: CurrencyAmount): CurrencyAmount {
            return this + (-other) // a - b = a + (-b)
        }
    
        operator fun times(scale: Int): CurrencyAmount {
            return CurrencyAmount(this.rawAmount * scale)
        }
    
        operator fun div(scale: Int): CurrencyAmount {
            return CurrencyAmount(this.rawAmount / scale)
        }
    
        operator fun times(scale: Double): CurrencyAmount {
            return CurrencyAmount(this.rawAmount * scale)
        }
    
        operator fun div(scale: Double): CurrencyAmount {
            return CurrencyAmount(this.rawAmount / scale)
        }
    
        override fun compareTo(other: CurrencyAmount): Int {
            return this.rawAmount.compareTo(other.rawAmount)
        }
    
        override fun toString(): String = String.format("¥ %.2f", rawAmount)
    
        fun toString(unit: CurrencyUnit, decimals: Int = 2): String {
            require(decimals >= 0) { "小数点必须大于0, 现在的小数点是$decimals" }
            val number = toDouble(unit)
            if (number.isInfinite()) {
                return number.toString()
            }
            val newDecimals = decimals.coerceAtMost(12)
            return DecimalFormat("0").run {
                if (newDecimals > 0) minimumFractionDigits = newDecimals
                roundingMode = RoundingMode.HALF_UP
                unit.prefix + " " + format(number)
            }
        }
    }
    
    //定义常用的一系列扩展属性(只读)
    val Double.cny get() = CurrencyAmount(this)
    val Long.cny get() = CurrencyAmount(this.toDouble())
    val Int.cny get() = CurrencyAmount(this.toDouble())
    
    val Double.usd get() = CurrencyAmount(this * CurrencyUnit.USD.exchangeRate)
    val Long.usd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.USD.exchangeRate)
    val Int.usd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.USD.exchangeRate)
    
    val Double.sgd get() = CurrencyAmount(this * CurrencyUnit.SGD.exchangeRate)
    val Long.sgd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.SGD.exchangeRate)
    val Int.sgd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.SGD.exchangeRate)
    
    val Double.hkd get() = CurrencyAmount(this * CurrencyUnit.HKD.exchangeRate)
    val Long.hkd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.HKD.exchangeRate)
    val Int.hkd get() = CurrencyAmount(this.toDouble() * CurrencyUnit.HKD.exchangeRate)
    
    

    相对而言更简单了,我们不需要想空间转换那样叠加使用,也不需要判断是否超过某一个单位,也无需自动格式化单位。

    我们只需要简单的定义单位转换,并且实现简单语义化的加减乘除等操作,方便金额的计算。

    如何使用?

        val money1 = 100.usd
        YYLogUtils.w("格式化金额人民币:${money1.toString()}")
        YYLogUtils.w("格式化金额新加坡:${money1.toString(CurrencyUnit.SGD)}")
        YYLogUtils.w("格式化金额港币:${money1.toString(CurrencyUnit.HKD)}")
    
        val money2 = 100.50.hkd
    
        YYLogUtils.w("100美元加100.5港币等于:${(money1+money2).toString()}")
    
    

    打印如下:

    image.png

    当然这是试验性质,实际的汇率是根据后端返回实时变动的,切不可直接生搬硬套。

    后记

    除此之外常规的扩展方法更是应用广泛,比如RV的扩展,FindView的扩展,SP的扩展,DataStore的扩展,Intent的扩展,Dialog扩展等等层出不穷。

    扩展属性对于一些简单的逻辑也更方便,更简洁。一般我们需要一个值可以直接用扩展属性,如果要进行逻辑运算那么可以用扩展方法,两者结合使用可以实现很多骚功能。

    本文的几个例子只是使用了基本的扩展属性和扩展方法,大家有兴趣可以自行练习一下。本文的代码仅用于学习交流,如果要用于实战我推荐你最好自己参照着实现对应的逻辑。

    相关文章

      网友评论

          本文标题:从Dart到Kotlin扩展函数的基本常规操作及应用场景

          本文链接:https://www.haomeiwen.com/subject/cjbjgdtx.html