大家有沒有想過,如果我要寫一個把數字呈現給使用者的function,我需要考慮到輸入的數字的資料型態之後再餵到function內,像如果我要轉換資料型態為整數、浮點數(小數)兩種,我就需要寫兩個function。
例如下面這個例子:
package main
import "fmt"
func showInt(num int64) {
fmt.Println(num)
}
func showFloat(num float64) {
fmt.Println(num)
}
func main() {
var sum1 int64 = 28
var sum2 float64 = 29.5
showInt(sum1)
showFloat(sum2)
}
- 執行結果:
但明明裡面執行的內容都差不多,有沒有一個方法可以把他們合起來寫成一個function呢?答案是有的,既然內容差不多,就把他們寫在同一個function吧,為甚麼這麼執著簡化function呢?這就要講到軟體工程領域中有很多工程師寫code寫到變得有程式潔癖XD,就衍生出了一個DRY原則。
DRY原則
在軟體工程領域中有一個DRY原則,我們可以看看維基百科中是怎麼描述DRY的Don’t repeat yourself,全名叫 “Don’t repeat yourself”。
把可能發生變化的重複資訊,用不太可能發生變化的抽象資訊替代,而泛型就是符合DRY的好方法之一。
來簡單化function吧
呈上一段的程式碼,我們可以用泛型把它簡化~ 讓兩個function統整為一個function,程式碼參考至初探 golang 1.18 generics 功能,原來1.18版的golang才把泛型引入XD
泛型方法
package main
import "fmt"
func show[num int64 | float64](s num) {
fmt.Println(s)
}
func main() {
var sum1 int64 = 28
var sum2 float64 = 29.5
show(sum1)
show(sum2)
}
- 執行結果:
你看~是不是乾淨很多!當然在還是有其他招可以取代泛型,來看看另一種方法-斷言 Assertion。
Assertion方法
package main
import "fmt"
func show(num any) {
switch s := num.(type) {
case int64:
fmt.Println(s)
case float64:
fmt.Println(s)
}
}
func main() {
fmt.Println("go 1.18 Generics Example")
var sum1 int64 = 28
var sum2 float64 = 29.5
show(sum1)
show(sum2)
}
- 執行結果:
其實兩種方法都是重構程式碼的好方法,我自己也另外做了壓力測試,看兩著執行速度會不會差很多,以下是執行結果,我把兩種方各執行了三次,每次執行將他們的show function 各使用了10000次,單位為ns/op,意思為一個指令要執行幾奈秒,只看ns/op看起來斷言方法的效能比較好一點…,那我要泛型到底要幹嘛,笑死。好吧也可能是我測試方法有誤,有甚麼因素沒有考慮到XD
Generic(泛型)
(1)
1000000000 0.9029 ns/op
PASS
ok example1 56.259s
(2)
1000000000 0.8784 ns/op
PASS
ok example1 64.238s
(3)
1000000000 0.9074 ns/op
PASS
ok example1 63.307s
Assertion(斷言)
(1)
1000000000 0.8786 ns/op
PASS
ok example1 65.471s
(2)
1000000000 0.8769 ns/op
PASS
ok example1 64.095s
(3)
1000000000 0.8761 ns/op
PASS
ok example1 61.927s
說實話兩者跑的結果是差不多的,選一個喜歡的方法來重構程式碼吧XD。但後來我工作上有個需求也是要重構程式碼,我一開始選擇使用了泛型,但發現泛型若使用物件當參數,竟然不能操作物件,像是使用他的屬性之類的操作,所以只能改用斷言的方式來重構程式碼了…以下為程式碼:
package main
import "fmt"
type apple struct {
name string
age int
}
func show[num int64 | float64 | *apple](s num) {
fmt.Println(s.name)
fmt.Println(s.age)
}
func main() {
apple1 := &apple{name: "andy", age: 26}
show(apple1)
}
- 執行結果:
之後改用斷言來解決的程式碼:
package main
import "fmt"
type apple struct {
name string
age int
}
func show(num any) {
switch s := num.(type) {
case *apple:
fmt.Println(s.name)
fmt.Println(s.age)
}
}
func main() {
apple1 := &apple{name: "andy", age: 26}
show(apple1)
}
- 執行結果:
重點回顧
- Generic是什麼?
- Assertion是什麼?
- 兩者差別?
總結
結論就是Assertion斷言比較讚,可以接受物件得屬性當參數,用Assertion斷言的方式重構才是聰明人…哈哈哈哈