美文网首页
一个减法的故事:Kotlin 扩展函数 ,Operator 和

一个减法的故事:Kotlin 扩展函数 ,Operator 和

作者: pdog18 | 来源:发表于2018-07-06 17:17 被阅读72次

    前言

    在写自定义控件的时候,有时会需要对PointF对象进行一定操作,计算两个点之间的水平间距和垂直间距。

    简化需求也就是要算出两个点之间的差值。

    用代码实现大概是这样的

    fun minusPoint(p1: PointF, p2: PointF): PointF {
        val dx = p1.x - p2.x
        val dy = p1.y - p2.y
        return PointF(dx, dy)
    }
    
    //使用
    val p = minusPoint(p1,p2)
    

    第一次修改

    这样的写法太Java了,因为我们用到的是Kotlin,我们可以改成这样

    fun minusPoint(p1: PointF, p2: PointF): PointF = PointF(p1.x - p2.x, p1.y - p2.y)
    
    //使用
    val p = minusPoint(p1,p2)
    

    第二次修改

    当然,这样也不够好。我们使用Kotlin的扩展函数为PointF这个对象添加上一个扩展函数

    fun PointF.minusPoint(p2: PointF): PointF = PointF(this.x - p2.x, this.y - p2.y)
    
    //使用
    val p = p1.minusPoint(p2)
    

    这样的调用看起来可读性高了非常多。

    第三次修改

    因为PointF自带了offset的方法

    public final void offset(float dx, float dy) {
      x += dx;
      y += dy;
    }
    

    所以我们将可以改成这个样子

    fun PointF.minusPoint(p2: PointF): PointF = PointF().apply {
        this.offset(-p2.x, -p2.y)
    }
    
    //使用
    val p = p1.minusPoint(p2)
    

    第四次修改

    有编程经验的小伙伴可能从第一次就发现了这个函数的一个“问题”,就是每次都会创建一个新的PointF对象。所以我们还可以对它进行一次“优化”

    fun PointF.minusPoint(p2: PointF): PointF = this.apply {
        this.offset(-p2.x, -p2.y)
    }
    
    //使用
    val p = p1.minusPoint(p2)
    

    这样每次调用都不会产生新的对象,直接使用原来的对象就可以了。一切都看起来很美妙。

    第五次修改

    我们再次回到我们一开始的时候,我们一开始需要解决的问题是“计算两个点的差值”,那么从语义上来讲。是不是可以简单的描述成为这样

    val p1: Point
    val p2: Point
    val p = p1 - p2 
    

    了解Kotlin 的operator的同学可能从第一次看到需求的时候就想到了-操作符。

    很明显 ktx中就有PointF的扩展操作符。

    /**
     * Offsets this point by the negation of the specified point and returns the result
     * as a new point.
     */
    inline operator fun PointF.minus(p: PointF): PointF {
        return PointF(x, y).apply {
            offset(-p.x, -p.y)
        }
    }
    
    //使用
    val p = p1 - p2
    

    再一次被Kotlin 甜到 !

    第六次修改

    细心的朋友发现,这个扩展操作符每次都返回了一个新的对象。

    那是不是ktx这个函数写的不好?

    其实不是这样的,现在回到我们的第四次的“优化”。

    fun PointF.minusPoint(p2: PointF): PointF = this.apply {
        this.offset(-p2.x, -p2.y)
    }
    
    //使用
    val p = p1.minusPoint(p2)
    

    现在我们来考虑一个问题,我们使用了p1对象减去p2的获得了一个对象p ,这时p其实就是p1,而它们的属性此时已被改变。如果这时,再去使用p1去做一些其他操作,显然就和预期得到的结果不一样了。

    发现问题所在了吗?我们的优化“减法”改变被减数,这显然是不合理的。

    所以我们在第五次的修改是不太合理的,但是我又不想用第六次的方案,因为它的确额外的对象,我就是饿死,死在外面,也不会吃这个语法糖的?!。

    那么应该怎么办呢?

    上面我们说到,一个减法是不应该去改变被减数的,减法得到的值理所当然是一个新的值。

    那么是否我们就只能这样了呢?当然不是,我们再次回到我们的需求,“获得两个点之间的差值”,其实这句需求还可以再增加完善一些,“获得两个点之间的差值,为了不产生新的对象可以直接修改其中一个点的值”

    那么到这里可以发现,我们有一个非常合适的操作符来描述它,也就是 -=

    直接来上代码

    inline operator fun PointF.minusAssign(p: PointF) {
        this.apply {
            offset(-p.x, -p.y)
        }
    }
    
    //使用,没有返回值
    p -= p2
    

    btw,由于传入的参数不是函数类型,这里的inline是多余的。

    由于没有返回值,那么我们可以这样调用

    val p1 = p.apply {
        this -= center
    }
    

    至此,我们的减法就算完成了。

    通过这个减法,我得到了什么?

    • 了解了kotlin的operator写法
    • 了解了kotlin的inline的一些规则
    • 函数如果会对传入参数进行修改,需要谨慎是否真的应该这样做。

    相关文章

      网友评论

          本文标题:一个减法的故事:Kotlin 扩展函数 ,Operator 和

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