前言
在寫自定義控制元件的時候,有時會需要對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的一些規則
- 函式如果會對傳入引數進行修改,需要謹慎是否真的應該這樣做。