一个减法的故事:Kotlin 扩展函数 ,Operator 和
前言
在写自定义控件的时候,有时会需要对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的一些规则
- 函数如果会对传入参数进行修改,需要谨慎是否真的应该这样做。