寫一個系統程式時,碰到一個問題如下: 因為同一個Struct中的一個變數,我們姑且叫它變數一,有可能是type A 或是 type B,根據type A或 type B會執行各自不同的功能,但因為同一個struct中還有許多其他相同的共用變數及method,因此不想將該物件拆成(事實上,以筆者的系統也不能拆成兩個)不同的struct type,因此用了interface{}來作為變數一的type,因此能順利的將不管是type A 或type B assign給變數一(從乾淨程式碼的角度來看,這也許不是個好做法,對於後來程式維護來講將提高難度),但能賦值後,於執行到該變數一時,要呼叫它的功能或變數,卻會發現因為type=interface{},而無法呼叫(錯誤訊息: type interface{} has no field or method XXXX)。
後來用了Switch .(type)這樣的作法來處理,因此這邊用一個簡單的例子來說明如何使用switch .(type)來解決: 於runtime時,動態(Dynamic)判斷物件是屬於哪個type,並進而用該type的方法來執行功能。
Switch .(type)
Switch .(type)能判斷所指定的變數是屬於哪個type,例如是Int,string,或甚至是我們自己定義的Type A,而能判斷是屬於哪個type後,並能給一個變數=所指定的變數並cast成該變數真正的type (ex: c:= object.(type),利用.(type)達成cast,此時c 就是實際的object被assign的type,而不再是Interface{} type的變數),接著利用switch判斷該變數屬於哪個type( ex: case *typeA: …. case *typeB:….),就能呼叫該type原本的變數及方法囉(ex: c.Variable)。
Demo Code :
下面一個簡單的例子來展示上面所說的,有一個變數InputB 被設為interface{}型別,可能被assign為InputBType1 或 InputBType2兩種之一(這邊Demo用rand來產生不確定性),最後用switch .(type)的方式來判斷使屬於哪個type,並使用該type的方法及印出該type的變數值。(注意:case中使用的是指標(pointer, *)來做判斷)。
package main
import (
"fmt"
"math/rand"
"time"
)
type DynamicTypeInput struct {
InputA int
InputB interface{}
}
type InputBType1 struct {
VarInt int
}
func (T *InputBType1) ShowVar(){
fmt.Println(T.VarInt)
}
type InputBType2 struct {
VarString string
}
func (T *InputBType2) ShowVar(){
fmt.Println(T.VarString)
}
func main() {
var object1 = new(DynamicTypeInput)
object1.InputB = GenerateType()
//object1.InputB.VarInt // error message:(type interface{} has no field or method VarInt))
//how to get and use InputB's variable and method
switch c := object1.InputB.(type) {
case *InputBType1:
c.ShowVar()
fmt.Println("In Type1,variable value=", c.VarInt)
case *InputBType2:
c.ShowVar()
fmt.Println("In Type2,variable value=", c.VarString)
default:
fmt.Println(c)
}
}
func GenerateType() interface{} {
rand.Seed(time.Now().UnixNano())
var typeChoice = rand.Intn(2)
fmt.Println("generate number is :", typeChoice)
if typeChoice == 1 {
return &InputBType1{VarInt: 1000}
} else {
return &InputBType2{VarString: "OneThousand"}
}
}附註:
為什麼能將interface{} type重新變成被賦值的type? 這是因為golang介面的處理方式,當一個物件被當成一個介面Assign後,其實還保留了物件原本的type的資訊,可以透過reflect或是這邊所展示的switch .(type)等方式來取得。