主要介紹在 Golang 中相對進階的用法,如interface、reflection、Tag。善用這些技巧可以使得程式碼更加簡潔。ex, 透過 interface 的技巧使得 func 的參數更加有彈性;使用 reflection 進一步資料屬於的型態、甚至達到無須知道 type 也能夠修改資料; Tag 讓你 mapping 資料更加方便。
Interface{} 型別轉換
在Golang 中Interface
資料結構是相當重要的,由於 Golang 屬於強型別語言,因此在func 中的 parameter 與 return value 時常會因為結構受限造成許多的不方便,然而interface
就是來解決問題。(它本身可以是Golang 語言中任一 type
進而解決該問題。) @playground
package main
import (
"fmt"
"strconv"
)
type str string
func (s str) String() string {
return string(s)
}
type Stringer interface {
String() string
}
func ToString(any interface{}) string {
if v, ok := any.(Stringer); ok {
return v.String()
}
switch v := any.(type) {
case int:
return strconv.Itoa(v)
case float64:
return strconv.FormatFloat(v, 'g', -1, 64)
}
return "???"
}
func main() {
var ex int = 1
fmt.Println(ToString(ex))
var ex2 float64 = 0.1
fmt.Println(ToString(ex2))
var ex3 Stringer = str("1")
fmt.Println(ToString(ex3))
}
如上述程式碼,實作ToString 方法時,需要傳入各種型態(type)的參數,此時Interface 的彈性就派上用場了,藉由還原的語法進行實作,繞過強型別參數型態固定的問題。
Interface{} 多形
此外在Golang中若要做到 abstract method 的話則也需要 interface
,它提供抽象方法的功能,並且可以在compile time 就能排除抽象方法實作上部份錯誤,ex. 少定義方法等...。換句話說,在Golang中實作多型
須仰賴interface
。@playground
package main
import (
"fmt"
)
type Car interface {
AddOil(gallon int)
Run()
}
type VovoCar struct {
}
func (v *VovoCar) AddOil(gallon int) {
fmt.Printf("vovo car add %d gallon\n", gallon)
}
func (v *VovoCar) Run() {
fmt.Printf("vovo car add run\n")
}
type ToyotaCar struct {
}
func (t *ToyotaCar) AddOil(gallon int) {
fmt.Printf("Toyota car add %d gallon\n", gallon)
}
func (t *ToyotaCar) Run() {
fmt.Printf("Toyota car add run\n")
}
func main() {
var c Car = &VovoCar{}
var c2 Car = &ToyotaCar{}
c.AddOil(10)
c.Run()
c2.AddOil(100)
c2.Run()
}
Reflection
是一種用於描述程式語言的工具。由於在Golang 中任何型別都可以是一種Interface
,因此時常需要與Reflection
進行搭配,故筆者認為是一種Interface
的配套工具。主要有三種用法:
package main
import (
"fmt"
"reflect"
)
type Example struct {}
func main() {
fmt.Println(reflect.TypeOf(&Example{})) // *main.Eaxmple
}
package main
import (
"fmt"
"reflect"
)
type Example struct {
name string
}
func main() {
val := reflect.ValueOf(&Example{ name: "Example name"}).Interface().(*Example)
fmt.Println(val) // &{Example name}
}
Ptr
。reflect.ValueOf.Elem.Field
找出struct filed 的位置進而修改資料。package main
import (
"fmt"
"reflect"
)
type Example struct {
Id string
Name string
}
func ChangeValue(dest interface{}) {
valDest := reflect.ValueOf(dest)
for i := 0; i < valDest.Elem().NumField(); i++ {
if i == 0 {
valDest.Elem().Field(i).Set(reflect.ValueOf("change_id"))
} else {
valDest.Elem().Field(i).Set(reflect.ValueOf("change_name"))
}
}
}
func main() {
destVal := &Example{Id: "test_id", Name: "test_name"}
fmt.Println(destVal) // &{test_id test_name}
ChangeValue(destVal)
fmt.Println(destVal) // &{change_id change_name}
}
在Golang Struct 資料結構中,可自定義 Tag,有點類似於其他語言的Annotation,例如在判讀 Json 的Key 值時,須利用`json:"
name"`
的方式填入。然而在學習Golang 的初期時常會勿以為 Tag 是不可定義的,因為並不清楚如何取用。然而Reflection 工具此時就得到了一大作用,由於是描述程式語言的工具,因此可透過該工具將Tag 取出。@playground
package main
import (
"fmt"
"reflect"
)
type Example struct {
Id string `json:"id"`
}
func main() {
jsonExample := &Example{Id: "test"}
field := reflect.TypeOf(*jsonExample).Field(0)
fmt.Printf("name: %v ,tag:'%v'\n", field.Name, field.Tag)
// name: Id ,tag:'json:"id"'
}
在Golang 語言中除了正規的正常的寫法之外,也提供低階的程式設計模式,如可直接調用Unix 系統的C程式的cgo。但這種方式是不被建議使用的,因為直接調用很容易出現非預期的錯誤,除非特殊需要。若要Golang 中使用這一類低階的程式設計,則需宣告import "unsafe"
字樣,語意上表示從外部匯入unsafe 套件,但實際上是由編譯器直接調用隱藏功能。ex: net, syscall, os, runtime 等大多程式設計不太需要用到的。
*ArbitraryType
而 ArbitraryType 的定義為type ArbitraryType int
,因此實際上也是一種 Integer 型別。指的是在編譯器端,對於程式碼編譯後,針對指標 (Pointer) 資料結構進行優化,並計算出需要多少的Heap 儲存。往往在Golang 中,時常使用到指標類型的結構,然而只要有該函式之外的呼叫,編譯器則會預先預先多配發空間,進而觸發GC(Grabage Collection)機制。
例如以下圖式為例: 單純透過指標的方式印出整數陣列,正規寫法如Listing 1 直接使用外部func 印出,優化做法透過 unsafe 的方式呼叫外部func。
在Golang中使用unsafe 直接調用Compile 的Pointer編譯器則不會預先產出Heap,也因此能避免該問題,但壞處是可能會遇到預期之外的錯誤(警語)。如上圖程式碼,都只是印出數字,但透過 unsafe pointer 調用的,使用的記憶體大小是預設的50倍,Heap 的大小是 3倍(下圖所示)。
[1] Wang, Cong, et al. "Escape from escape analysis of Golang." Proceedings of the ACM/IEEE 42nd International Conference on Software Engineering: Software Engineering in Practice. 2020.
[2] https://go.dev/blog/laws-of-reflection
[3] https://research.swtch.com/interfaces